Skip to main content
 首页 » 编程设计

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

2024年08月12日30langtianya

就在我理解元类的时候...

免责声明:我在发帖前四处寻找答案,但我找到的大部分答案都是关于调用 super() 的去另一个@classmethod在 MRO 中(不涉及元类),或者,令人惊讶的是,他们中的很多人都试图在 metaclass.__new__ 中做一些事情。或 metaclass.__call__这意味着该类尚未完全创建。我很确定(假设是 97%)这不是其中一个问题。


环境:Python 3.7.2


问题: 我有一个元类 FooMeta定义了一个方法 get_foo(cls) , 一类 Foo它是从那个元类构建的(所以是 FooMeta 的一个实例)并且有一个 @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() 有什么不理解的?这让我觉得这些结果不合逻辑?

编辑:进一步测试表明 Foo2 上的方法作为类方法或实例方法不会改变结果。

编辑 2:感谢@chepner 的回答,我认为问题在于 super()正在返回一个表示 Foo 的 super 对象(这是用 super().__thisclass__ 验证的)我期待 super().get_foo()表现(甚至打电话)get_attr(Foo, 'get_foo')在幕后。好像不是……我还在想为什么,但是越来越清楚了:)

请您参考如下方法:

Foo 可能有一个 get_foo 方法,但是 super 并不是为了检查父类(super class)有哪些属性而设计的。 super 关心哪些属性源自在父类(super class)中。


要理解 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的方法解析顺序,类检查的顺序属性查找,转到 (D, B, C, A, object)。

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

super 不查看 B 有哪些属性,而是直接查看 B 的 __dict__,因此它只考虑 B 实际提供的属性B 的父类(super class)或元类。


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