简体   繁体   English

在类的类方法中调用 super() 以获取元类方法

[英]Calling super() inside a class' classmethod to get at the metaclass method

Just when I though I understood metaclasses...就在我虽然理解元类的时候......

Disclaimer : I have looked around for an answer before posting, but most of the answers I have found are about calling super() to get at another @classmethod in the MRO (no metaclass involved) or, surprisingly, a lot of them were about trying to do something in metaclass.__new__ or metaclass.__call__ which meant the class wasn't fully created yet.免责声明:我在发布之前已经四处寻找答案,但我发现的大多数答案都是关于调用super()以获取 MRO 中的另一个@classmethod (不涉及元类),或者令人惊讶的是,其中很多都是关于试图在metaclass.__new__metaclass.__call__中做某事,这意味着 class 尚未完全创建。 I'm pretty sure (let's say 97%) that this is not one of those problems.我很确定(比如说 97%)这不是这些问题之一。


Environment : Python 3.7.2环境:Python 3.7.2


The problem : I have a metaclass FooMeta that defines a method get_foo(cls) , a class Foo that is built from that metaclass (so an instance of FooMeta ) and has a @classmethod get_bar(cls) .问题:我有一个元类FooMeta ,它定义了一个方法get_foo(cls) ,一个 class Foo ,它是从该元类构建的(因此是FooMeta的一个实例)并且有一个@classmethod get_bar(cls) Then another class Foo2 that inherits from Foo .然后是另一个继承自Foo的 class Foo2 In Foo2 , I subclass get_foo by declaring it a @classmethod and calling super() .Foo2中,我通过将get_foo声明为@classmethod并调用super()来继承它。 This fails miserably...这失败得很惨……

ie with this code即使用此代码

class FooMeta(type):
    def get_foo(cls):
        return 5


class Foo(metaclass=FooMeta):
    @classmethod
    def get_bar(cls):
        return 3


print(Foo.get_foo)
# >>> <bound method FooMeta.get_foo of <class '__main__.Foo'>>
print(Foo.get_bar)
# >>> <bound method Foo.get_bar of <class '__main__.Foo'>>


class Foo2(Foo):
    @classmethod
    def get_foo(cls):
        print(cls.__mro__)
        # >>> (<class '__main__.Foo2'>, <class '__main__.Foo'>, <class 'object'>)
        return super().get_foo()

    @classmethod
    def get_bar(cls):
        return super().get_bar()


print(Foo2().get_bar())
# >>> 3
print(Foo2().get_foo())
# >>> AttributeError: 'super' object has no attribute 'get_foo'


The question : So with my class being an instance of the metaclass, and having verified that both class methods exist on the class Foo , why aren't both calls to the super().get_***() working inside Foo2 ?问题:所以我的 class 是元类的一个实例,并且已经验证了 class 方法都存在于Foo2 Foo中,为什么两个调用都不能在super().get_***()内部工作? What am I not understanding about metaclasses or super() that's preventing me from finding these results logical?我对元类或super()有什么不了解的地方,这会阻止我发现这些结果是合乎逻辑的?

EDIT: Further testing shows that the methods on Foo2 being class methods or instance methods doesn't change the result.编辑:进一步的测试表明, Foo2上的方法是 class 方法或实例方法不会改变结果。

EDIT 2: Thanks to @chepner's answer, I think the problem was that super() was returning a super object representing Foo (this is verified with super().__thisclass__ ) and I was expecting super().get_foo() to behave (maybe even to call) get_attr(Foo, 'get_foo') behind the scene.编辑2:感谢@chepner的回答,我认为问题在于super()正在返回一个代表Foo的超级 object (这已通过super().__thisclass__验证),我期待super().get_foo()表现(甚至可能在幕后调用) get_attr(Foo, 'get_foo') It seems that it isn't... I'm still wondering why, but it is getting clearer:)好像不是……我还在想为什么,但是越来越清楚了:)

Foo may have a get_foo method, but super isn't designed to check what attributes a superclass has. Foo可能有一个get_foo方法,但super并非旨在检查超类具有哪些属性。 super cares about what attributes originate in a superclass. super关心超类中的哪些属性。


To understand super 's design, consider the following multiple inheritance hierarchy:要了解super的设计,请考虑以下多个 inheritance 层次结构:

class A:
    @classmethod
    def f(cls):
        return 1
class B(A):
    pass
class C(A):
    @classmethod
    def f(cls):
        return 2
class D(B, C):
    @classmethod
    def f(cls):
        return super().f() + 1

A, B, C, and D all have an f classmethod, but B's f is inherited from A. D's method resolution order, the sequence of classes checked for attribute lookup, goes (D, B, C, A, object) . A、B、Z0D61F8370CAD1D412F80B84D1D412F80B84D143E1257Z、D都有一个f类方法,但是B的f继承自A.D的方法解析顺序,检查属性查找的类顺序,为(D, B, C, A, object)

super().f() + 1 searches the MRO of cls for an f implementation. super().f() + 1cls的 MRO 中搜索f实现。 The one it should find is C.f , but B has an inherited f implementation, and B is before C in the MRO.它应该找到的是C.f ,但 B 具有继承的f实现,并且 B 在 MRO 中的 C 之前。 If super were to pick up Bf , this would break C 's attempt to override f , in a situation commonly referred to as the "diamond problem".如果super选择Bf ,这将破坏C覆盖f的尝试,这种情况通常被称为“钻石问题”。

Instead of looking at what attributes B has, super looks directly in B's __dict__ , so it only considers attributes actually provided by B instead of by B 's superclasses or metaclasses. super不是查看 B 有什么属性,而是直接查看 B 的__dict__ ,因此它只考虑B实际提供的属性,而不是B的超类或元类。


Now, back to your get_foo / get_bar situation.现在,回到你的get_foo / get_bar情况。 get_bar comes from Foo itself, so super().get_bar() finds Foo.get_bar . get_bar来自Foo本身,因此super().get_bar()找到Foo.get_bar However, get_foo is provided not by Foo , but by the FooMeta metaclass, and there is no entry for get_foo in Foo.__dict__ .但是, get_foo不是由Foo提供的,而是由FooMeta元类提供的,并且Foo.__dict__中没有get_foo的条目。 Thus, super().get_foo() finds nothing.因此, super().get_foo()什么也没找到。

Note 1注1

Just to be clear:只是要清楚:

  • Foo does not inherit from FooMeta . Foo继承自FooMeta
  • FooMeta is not a super-class of Foo FooMeta不是Foo的超类

super will not work. super不行。

Note 2笔记2

Now that note (1) is out of the way, if you want to access a metaclass method from inside of a method of an instance of the metaclass, you can do it like this:现在注释 (1) 已经不碍事了,如果你想从元类实例的方法内部访问元类方法,你可以这样做:

class FooMeta(type):
    _foo = 5

    def get_foo(cls):
        print("`get_foo` from `FooMeta` was called!")

    class Foo(metaclass=FooMeta):

        @classmethod
        def bar(Foo):
            FooMeta = type(Foo)
            FooMeta_dot_getfoo = FooMeta.get_foo
            FooMeta_dot_getfoo(Foo)

        def baz(self):
            Foo = type(self)
            FooMeta = type(Foo)
            FooMeta_dot_getfoo = FooMeta.get_foo
            FooMeta_dot_getfoo(Foo)

    Foo.bar()
    foo = Foo()
    foo.baz()

The output is: output 是:

`get_foo` from `FooMeta` was called!
`get_foo` from `FooMeta` was called!

Note 3注3

If you have a classmethod with the same name as a method in the metaclass, why does the metaclass method NOT get called?如果您有一个与元类中的方法同名的类方法,为什么调用元类方法? Consider the following code:考虑以下代码:

class FooMeta(type):
    def get_foo(cls):
        print("META!")

class Foo(metaclass=FooMeta):
    @classmethod
    def get_foo(cls):
        print("NOT META!")

Foo.get_foo()

The output is NOT META! output NOT META! In the following discussion, assume that:在以下讨论中,假设:

  • foo is instance of Foo fooFoo的实例
  • Foo is instance of FooMeta FooFooMeta的实例

For the first time in this post, I will have pseudo code, not python.在这篇文章中,我将第一次使用伪代码,而不是 python。 Don't try to run the following.不要尝试运行以下命令。 __getattribute__ sorta looks like the following: __getattribute__如下所示:

class FooMeta(type):
    def get_foo(Foo):
        print("META!")

class Foo(metaclass=FooMeta):
    @classmethod
    def get_foo(Foo):
        print("NOT META!")

    def __getattribute__(foo, the string "get_foo"):
        try:
            attribute = "get_foo" from instance foo
        except AttributeError:
            attribute = "get_foo" from class Foo

        # Begin code for handling "descriptors"
        if hasattr(attribute, '__get__'):
            attr = attribute.__get__(None, Foo)
        # End code for handling "descriptors"

        return attribute

foo = Foo()
foo.get_foo() # prints "NOT META!"

get_foo = Foo.__getattribute__(foo, "get_foo")
get_foo.__call__()

You can actually ignore the stuff which says, " code for handling "descriptors" ."你实际上可以忽略那些说“ code for handling "descriptors" 。” I only included that for completeness.我只是为了完整性而将其包括在内。

Note that nowhere does __getattribute__ say, "get get_foo from the meta class."请注意, __getattribute__ getattribute__ 没有说“从元 class 中获取get_foo ”。

  1. First, We try to get get_foo from the instance.首先,我们尝试从实例中获取get_foo Maybe get_foo is a member variable.也许get_foo是一个成员变量。 Maybe one instance has get_foo = 1 and another instance has get_foo = 5 The computer does not know.也许一个实例有get_foo = 1而另一个实例有get_foo = 5计算机不知道。 The computer is stupid.电脑很傻。
  2. The computer realizes that the instance doesn't have a member variable named get_foo .计算机意识到该实例没有名为get_foo的成员变量。 It then says, "ah ha! I bet that get_foo belongs to the CLASS."然后它说,“啊哈!我敢打赌get_foo属于 CLASS。” So, it looks there, and lo-and-behold, there it is: Foo has an attribute named get_foo .所以,它看起来在那里,你瞧,它就在那里: Foo有一个名为get_foo的属性。 FooMeta also has an attribute called get_foo , but who cares about that. FooMeta也有一个名为get_foo的属性,但谁在乎呢。

Something to focus on is that:需要关注的是:

  • Foo has an attribute named get_foo Foo有一个名为get_foo的属性
  • MetaFoo has an attribute named get_foo MetaFoo有一个名为get_foo的属性

They both have attributes named get_foo , but Foo and MetaFoo are different objects.它们都有名为get_foo的属性,但FooMetaFoo是不同的对象。 It's not as if the two get_foo s are shared.两个get_foo共享的。 I can have obj1.x = 1 and obj2.x = 99 .我可以有obj1.x = 1obj2.x = 99 No problem.没问题。

FooMeta has its own __getattribute__ method. FooMeta有自己的__getattribute__方法。 Before I talked about Foo.__getattribute__ , but now let's talk about the MeTa __getattribute__在我谈到Foo.__getattribute__ ,现在让我们谈谈MeTa __getattribute__

class FooMeta(type):
    def get_foo(Foo):
        print("META!")

    def __getattribute__(Foo, the string "get_foo"):
        try:                                            # LINE 1
            attribute = "get_foo" from class Foo        # LINE 2
        except AttributeError:                          # LINE 3
            attribute = "get_foo" from class FooMeta    # LINE 4
                                                        # LINE 5
        # Begin code for handling "descriptors"
        if hasattr(attribute, '__get__'):
            attr = attribute.__get__(None, Foo)
        # End code for handling "descriptors"

        return attribute

class Foo(metaclass=FooMeta):
    @classmethod
    def get_foo(Foo):
        print("NOT META!")

Foo.get_foo()

get_foo = FooMeta.__getattribute__(Foo, "get_foo")
get_foo.__call__() 

The order of events:事件顺序:

  • Lines 1 and 2 happen第 1 行和第 2 行发生
  • Lines 3, 4, & 5 do not happen第 3、4 和 5 行不会发生
  • You can ignore the stuff about descriptors, because none of the different get_foo s in this problem have a __get__ method您可以忽略有关描述符的内容,因为此问题中的不同get_foo都没有__get__方法

Okay now?现在好吗? Why only lines 1 & 2?为什么只有第 1 行和第 2 行? Because you made a @classmethod silly!因为你把@classmethod傻了! We check Foo to see if it has a get_foo and it does?我们检查Foo看它是否有一个get_foo并且它有? Why check for class attributes if we find an instance attribute first?如果我们首先找到实例属性,为什么还要检查 class 属性? We always check to see if an attribute belong to the instance ( Foo ) first-and-foremost before checking if maybe there happens to be only one copy of a static member variable belonging to the class ( FooMeta ) and shared by all of the instances.我们总是首先检查一个属性是否属于实例( Foo然后再检查是否可能恰好只有一个属于FooMeta并由所有实例共享的 static 成员变量的副本.

Note that if Foo does not have a get_foo then FooMeta.__getattribute__(Foo, "get_foo") will return get_foo from the metaclass because the first attempt (getting it from the instance) failed.请注意,如果 Foo 没有get_fooFooMeta.__getattribute__(Foo, "get_foo")将从元类返回get_foo因为第一次尝试(从实例中获取它)失败。 You kinda blocked that option out by giving the instance something of the same name as the class's static member variable.您通过为实例提供与类的 static 成员变量相同的名称来阻止该选项。

class K:

    im_supposed_to_be_shared = 1

    def __init__(self, x):
        # NOPE!
        self.im_supposed_to_be_shared = x
        # maybe try type(self)

obj1 = K(14)
obj2 = K(29)
print(obj1.im_supposed_to_be_shared)
print(obj2.im_supposed_to_be_shared)
print(K.im_supposed_to_be_shared)

Prints:印刷:

14
29
1

does NOT print:打印:

29
29
29

Note that if you want to set a static class member variable, instance.mem_var = 5 is a very ⱽᵉᴿʸ bad idea.请注意,如果要设置 static class成员变量, instance.mem_var = 5是一个非常糟糕的主意。 You will give the instance a new member variable, and the class static (shared) member variable will be shadowed.您将为instance提供一个新的成员变量,并且 class static(共享)成员变量将被隐藏。 You can fix that with something like this:你可以用这样的东西来解决这个问题:

def __setattr__(self, attr_name, attr_val):
    if hasattr(type(self), attr_name):
        setattr(type(self), attr_name, attr_val)
    else:
        super_class = inspect.getmro(type(self))[1]
        super_class.__setattr__(self, attr_name, attr_val)

Then your lil' compy will print:然后你的 lil' compy 将打印:

29
29
29

Note 4注4

class Foo:
    @classmethod
    def funky(cls):
        pass

is NOT MetaClass.funky = funky .不是MetaClass.funky = funky Instead, it's:相反,它是:

def funky(cls)
   pass
Funky = classmethod (funky)

... which is almost the same as: ...这几乎与:

def funky(cls):
     pass
funky = lambda self, *args, **kwargs: funky(type(self), *args, **kwargs)

The moral of note 4s story is that classmethod funky is an attribute of Foo and not an attribute of FooMeta note 4s 故事的寓意是classmethod funkyFoo的属性,而不是FooMeta的属性

get_foo is not an attribute of Foo , but rather an attribute of type(Foo) : get_foo不是Foo的属性,而是type(Foo)的属性:

>>> 'get_foo' in Foo.__dict__
False
>>> 'get_foo' in type(Foo).__dict__
True

So while Foo.get_foo will resolve to type(Foo).get_foo , super().get_foo does not because the proxy returned by super() is similar to Foo , but is not Foo itself.因此,虽然Foo.get_foo将解析为type(Foo).get_foo ,但super().get_foo不会,因为super()返回的代理类似于Foo ,但不是Foo本身。

obj.attr calls type(obj).__getattribute__(obj, 'attr') , that is obj.attr调用type(obj).__getattribute__(obj, 'attr') ,即

  • a) object.__getattribute__(obj, 'attr') , which looks up 'attr' in obj itself, as well as in the class of obj and its parents; a) object.__getattribute__(obj, 'attr') ,它在obj本身以及obj及其父对象的 class 中查找'attr' or或者
  • b) type.__getattribute__(obj, 'attr') if obj is a type instance, which looks up 'attr' in obj itself and its parents, as well as in the class of obj and its parents; b) type.__getattribute__(obj, 'attr')如果obj是一个type实例,它在obj本身及其父对象中查找'attr' ,以及在obj及其父对象的 class 中查找 'attr' ; or或者
  • c) super.__getattribute__(obj, 'attr') if obj is a super instance, c) super.__getattribute__(obj, 'attr')如果obj是一个super实例,
    • c.1) which looks up 'attr' in the class of instance and its parents past cls , if obj is super(cls, instance) , or c.1) 在instance的 class 及其父母过去cls中查找'attr' ,如果objsuper(cls, instance) ,或者
    • c.2) which looks up 'attr' in subclass itself and its parents past cls , if obj is super(cls, subclass) . c.2) 如果objsuper(cls, subclass) cls则在subclass本身及其父类中查找'attr' cls 。

When you call super().get_foo() in the class method Foo2.get_foo , which is equivalent to calling super(Foo2, cls).get_foo() , you are in case c.2), ie you are looking up 'get_foo' in cls itself and its parents past Foo2 , ie you are looking up 'get_foo' in Foo .当您在 class 方法Foo2.get_foo中调用super().get_foo()时,相当于调用super(Foo2, cls).get_foo()时,您是在 c.2) 的情况下,即您正在查找'get_foo'cls本身及其过去Foo2的父母中,即您在Foo中查找'get_foo' That is why the call fails.这就是调用失败的原因。

You expect the call super().get_foo() in the class method Foo2.get_foo to succeed because you think it is equivalent to the call Foo.get_foo() which is in case b), ie you think you are looking up 'get_foo' in cls itself and its parents, as well as in the class of Foo ( FooMeta ) and its parents .您希望 class 方法Foo2.get_foo中的调用super().get_foo()成功,因为您认为它等同于在案例 b) 中的调用Foo.get_foo() ,即您认为您正在查找'get_foo'cls本身及其父级中,以及在Foo ( FooMeta ) 及其父级的 class 中

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 调用@classmethod抽象超类错误— Python - Calling @classmethod of abstract super class error — Python 在继承元类的类中调用super会引发错误 - Calling super in a class inheriting a metaclass throws an error 在Python 3中,不能在元类的方法内调用“ classmethod”对象 - 'classmethod' object is not callable inside a metaclass's method in Python 3 python子类方法调用用非类方法覆盖的类方法 - python child class method calling overridden classmethod with non-classmethod 在同一个 Python 类中调用方法、类方法、静态方法 - Calling method, classmethod, staticmethod in the same Python class `classmethod` 和元类方法有什么区别? - What are the differences between a `classmethod` and a metaclass method? 从元类调用的类方法中调用“ super”。__new__ - Invoking `super` in classmethod called from metaclass.__new__ 从另一个类调用一个类方法会导致元类冲突 - Calling a class method from another class causes metaclass conflict 在子 class 中调用 @classmethod 给出“TypeError: super(type, obj): obj must be an instance or subtype of type” - Calling a @classmethod in a child class give a 'TypeError: super(type, obj): obj must be an instance or subtype of type' 如何使用自定义类方法覆盖在元类中定义的特殊方法? - How can I override a special method defined in a metaclass with a custom classmethod?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM