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

时间:2019-11-05 02:08:01

标签: python python-3.x metaclass

就算我了解元类...

免责声明:发布之前,我一直在寻找答案,但是发现的大部分答案都涉及到致电super()来获取MRO中的另一个@classmethod (不涉及元类),或者令人惊讶的是,其中很多是试图在metaclass.__new__metaclass.__call__中做某事,这意味着该类尚未完全创建。我非常确定(假设是97%),这不是这些问题之一。


环境:Python 3.7.2


问题: 我有一个元类FooMeta,它定义了一种方法get_foo(cls),一个类Foo是从该元类(因此为FooMeta的一个实例)构建的,并且具有一个{{1} } @classmethod。然后是另一个从get_bar(cls)继承的类Foo2。在Foo中,我通过声明Foo2并调用get_foo来继承@classmethod的子类。这真是失败了...

即用此代码

super()


问题: 因此,以我的类为元类的实例,并确认两个类方法都存在于类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' 上,为什么对Foo的两个调用都不在super().get_***()内部工作? 我对元类或Foo2不了解,这使我无法发现这些结果是否合乎逻辑?

编辑:进一步测试表明,super()上的方法是类方法还是实例方法,不会改变结果。

编辑2:感谢@chepner的回答,我认为问题是Foo2返回了代表super()的超级对象(已通过Foo验证),我期望{ {1}}在幕后表现(甚至可以打电话)super().__thisclass__。似乎不是……我仍然想知道为什么,但是它越来越清晰了:)

4 个答案:

答案 0 :(得分:3)

Foo可能具有get_foo方法,但是super并非旨在检查超类具有的属性。 super关心超类中的起源属性。


要了解super的设计,请考虑以下多重继承层次结构:

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和D都具有f类方法,但是B的f继承自A。D的方法解析顺序,即检查属性查找的类的顺序,{ 1}}。

(D, B, C, A, object)super().f() + 1的MRO中搜索cls的实现。它应该找到的是f,但是B具有继承的C.f实现,并且B在MRO中位于C之前。如果f拿起super,在通常称为“钻石问题”的情况下,这将中断B.f覆盖C的尝试。

f并没有直接查看B的属性,而是直接在B的super中查找,因此它只考虑__dict__实际提供的属性,而不考虑B的属性超类或元类。


现在,回到您的B / get_foo情况。 get_bar来自get_bar本身,因此Foo找到super().get_bar()。但是,Foo.get_bar不是由get_foo提供的,而是由Foo元类提供的,并且FooMeta中没有get_foo的条目。因此,Foo.__dict__一无所获。

答案 1 :(得分:0)

注释1

请明确说明:

  • Foo不会 FooMeta继承。
  • FooMeta 不是 Foo
  • 的超类

super不起作用。

注释2

现在,注释(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()

输出为:

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

注释3

如果您有一个与元类中的方法同名的类方法,为什么要调用元类方法 NOT ?考虑以下代码:

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

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

Foo.get_foo()

输出为NOT META!,在以下讨论中,假定:

  • fooFoo的实例
  • FooFooMeta的实例

在本文中,我将第一次使用伪代码,而不是python。不要尝试运行以下内容。 __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__()

实际上,您可以忽略显示“ code for handling "descriptors"”的内容。我仅出于完整性考虑而包括在内。

请注意,__getattribute__哪里都没有说过“从元类中获取get_foo。”

  1. 首先,我们尝试从实例中获取get_foo。也许get_foo是成员变量。也许一个实例有get_foo = 1,而另一个实例有get_foo = 5计算机不知道。电脑很笨。
  2. 计算机意识到实例没有名为get_foo的成员变量。然后说:“啊哈!我敢打赌get_foo属于CLASS。”因此,它看上去就在那里,而且看上去也就这样:Foo有一个名为get_foo的属性。 FooMeta还有一个名为get_foo的属性,但谁在乎呢。

需要重点注意的是:

  • Foo的属性为get_foo
  • MetaFoo的属性为get_foo

它们 都具有名为get_foo的属性,但是FooMetaFoo是不同的对象。这似乎不是两个get_foo共享的。我可以有obj1.x = 1obj2.x = 99。没问题。

FooMeta有其自己的__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__() 

事件顺序:

  • 第1行和第2行发生
  • 第3、4和5行不会发生
  • 您可以忽略有关描述符的内容,因为此问题中不同的get_foo都没有__get__方法

好吧!为什么只有1和2行?因为您犯了一个@classmethod很傻!我们检查Foo来看看它是否有get_foo!如果先找到实例属性,为什么还要检查类属性?我们总是先检查属性是否属于实例(Foo 第一和最重要的 ,然后再检查是否可能只有一个副本属于该类(FooMeta)并由所有实例共享的静态成员变量。

请注意,如果Foo没有get_foo,则FooMeta.__getattribute__(Foo, "get_foo")将从元类返回get_foo,因为第一次尝试(从实例中获取)失败。您可以通过为实例提供与类的静态成员变量相同的名称来阻止该选项。

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)

打印:

14
29
1

不打印

29
29
29

请注意,如果您要设置静态类成员变量,instance.mem_var = 5是一个非常不明智的主意。您将为instance提供一个新的成员变量,并且该类静态(共享)成员变量将被遮盖。您可以使用以下方法解决此问题:

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)

然后您的小动物会打印:

29
29
29

注释4

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

MetaClass.funky = funky。而是:

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

...几乎与以下内容相同:

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

注释4s故事的寓意是classmethod funkyFoo的属性,而不是FooMeta的属性

答案 2 :(得分:0)

get_foo不是Foo的属性,而是type(Foo)的属性:

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

因此,尽管Foo.get_foo将解析为type(Foo).get_foo,但是super().get_foo并不是因为super()返回的代理类似于Foo,但不是{{ 1}}本身。

答案 3 :(得分:0)

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

  • a) object.__getattribute__(obj, 'attr'),它在 'attr' 本身以及 obj 及其父类中查找 obj;或
  • b) type.__getattribute__(obj, 'attr') if obj 是一个 type 实例,它在 'attr' 本身和它的父级以及类中查找 obj obj 及其父母;或
  • c) super.__getattribute__(obj, 'attr') 如果 objsuper 实例,
    • c.1) 在 'attr' 及其父级 instance 之后查找 cls,如果 objsuper(cls, instance),或者
    • c.2) 如果 'attr'subclass,它会在 cls 本身及其父项中查找 obj,如果 super(cls, subclass)super().get_foo()

当你在类方法Foo2.get_foo中调用super(Foo2, cls).get_foo(),相当于调用'get_foo',你在c.2)的情况下,即你在查找{{1} } 在 cls 本身及其过去 Foo2 的父项中,即您在 'get_foo' 中查找 Foo。这就是调用失败的原因。

您希望类方法 super().get_foo() 中的调用 Foo2.get_foo 成功,因为您认为它等同于调用 Foo.get_foo(),即 b) 中的调用,即您认为您正在寻找在 'get_foo' 本身和它的父级,以及在 cls (Foo) 和它的父级的类中向上 FooMeta