在`__init_subclass__`中使用`super()`无法找到父类的classmethod。

8
我尝试在__init_subclass__内访问父类的classmethod,但似乎不起作用。假设以下示例代码:
class Foo:
    def __init_subclass__(cls):
        print('init', cls, cls.__mro__)
        super(cls).foo()

    @classmethod
    def foo(cls):
        print('foo')


class Bar(Foo):
    pass

产生以下异常:

AttributeError: 'super' object has no attribute 'foo'
cls.__mro__显示Foo是其中的一部分:(<class '__main__.Bar'>, <class '__main__.Foo'>, <class 'object'>)。因此,我不明白为什么super(cls).foo()不能转发到Foo.foo。有人能解释一下吗?

super(cls) 更改为 super(cls, cls)。使用单参数形式,您最终会在 Foo 的父类(即 object)中查找 foo 方法。 - Aran-Fey
你为什么在这里使用 super(cls)?未绑定的超类很少有用,而且它们不是你在这里要找的。如果你只是想调用 clsfoo 类方法的父类,那就像实例方法一样使用 super(cls, cls).foo()。如果你想做一些不同的事情……那么呢? - abarnert
如果你真的想要解释为什么这样做不是你想要的,你需要一些背景知识才能解释。你知道什么是描述符吗?通常方法如何工作?超类在一般情况下如何工作?只是需要知道未绑定超类如何工作?还是需要解释其中更基本的部分之一? - abarnert
@abarnert 承认我以前从未使用过未绑定的超类,可能被文档搞混了:super([type[, object-or-type]]) 返回一个代理对象,将方法调用委托给类型的父类或兄弟类。[...]。所以我可能需要重新学习一下这个;其他部分我都很熟悉。但最终你提到的确是有道理的。 - a_guest
1个回答

14
一般的super对象(通常通过调用super(MyType, self)super()super(MyType, myobj)来获取)会记录创建它时的类型和对象。每当在super上查找属性时,它都会跳过方法解析顺序中的MyType,但如果找到一个方法,它就会将其绑定到该self对象。
未绑定的super没有self对象。所以,super(cls)会跳过MRO中的cls来查找方法foo,然后将其绑定到...哎呀,它没有可调用的对象。
那么,在哪些东西上可以调用classmethod?类本身,或者它的子类,或者该类或子类的实例。因此,这里的第二个参数可以是其中任何一个,最明显的是:
super(cls, cls)

这有点类似于静态方法(绑定的staticmethods实际上没有绑定到任何东西)和类方法(绑定的classmethods绑定到类而不是实例)之间的区别,但情况并不那么简单。


如果你想知道为什么未绑定的super不起作用,你就必须理解未绑定的super是什么。不幸的是,在文档中唯一的解释是:

如果省略第二个参数,则返回的超级对象未绑定。

这是什么意思?嗯,你可以尝试从第一原理出发,将其视为方法未绑定的并行概念(当然,在现代Python中,未绑定的方法不是一个东西),或者阅读C源代码,或者阅读原始的介绍2.2类类型统一性(包括纯Python super克隆)super对象有一个__self__属性,就像方法对象一样。而super(cls)缺少它的__self__,就像str.split一样。1 在使用未绑定的super时,不能像使用未绑定方法一样显式地使用它(例如,str.split('123', '2')'123'.split('2')相同,但super(cls).foo(cls)super(cls, cls).foo()不同)。但你可以隐式地使用它们,就像你一直无意识地使用未绑定方法一样。
如果您不知道如何工作, tl'dr是:当您评估myobj.mymeth时,Python查找mymeth,在myobj本身上找不到它,但在类型上找到它,因此它检查它是否是非数据描述符,如果是,则调用它的__get__方法将其绑定到myobj
因此,未绑定的方法2是非数据描述符,其__get__方法返回一个绑定方法。未绑定的@classmethod类似,但它们的__get__忽略对象并返回绑定到类的绑定方法。等等。

未绑定的super是非数据描述符,其__get__方法返回绑定的super


示例(感谢wim提出了最接近未绑定super用途的东西):

class A:
    def f(self): print('A.f')
class B(A):
    def f(self): print('B.f')
b = B()
bs = super(B)
B.bs = bs
b.bs.f()

我们创建了一个未绑定的超级{{bs}},将其附加到类型{{B}}上,然后{{b.bs}}是一个普通的绑定超级,所以{{b.bs.f}}就是{{A.f}},就像在{{B}}方法内部使用{{super().f}}一样。
为什么要这样做呢?我不确定。我曾经在Python中编写过各种可笑的动态和反射代码(例如用于透明代理到其他解释器),但我记不得曾经需要过未绑定的{{super}}。但是如果您需要它,它就在那里。

1. 这里稍微有点作弊。首先,在Python 3中,未绑定的方法已经不存在了——但是函数的工作方式相同,因此Python在以前使用未绑定的方法的地方现在使用它们。其次,str.split是一个C内置函数,在2.x版本中甚至不被正确地视为未绑定的方法——但在我们关心的这个问题上,它仍然像一个未绑定的方法。

2. 实际上只是普通的函数。


感谢您的详细解释。特别是您最后的评论:“未绑定的超类是非数据描述符,其__get__方法返回绑定的超类”,让我恍然大悟。 - a_guest
@a_guest 是的,这确实是关键——“未绑定的X”意味着"X.__get__返回一个绑定的X"。除了这个通用规则没有在任何地方说明,而且唯一一个特殊情况的文档在Python 3中已经不存在了,所以...不是最易发现的规则。顺便说一句,我认为应该有更好的方法来组织这个答案,以便帮助更多的人,但我想不出来;如果你有任何想法,那就太好了。 - abarnert
也许这是一个更一般的关于绑定和非绑定super之间相互作用的问题?有一个关于非绑定super的stackoverflow问题,但回答完全不相关;没有其他stackoverflow资源真正涉及到这个主题。但总的来说,我认为我会从解释MRO并激发使用(绑定)super开始,然后转向非绑定super,解释它是如何创建的,它是什么(以及描述符),转换为绑定super,它的目的以及一些用例(尽管这些似乎很难找到)。 - a_guest
@a_guest 这样肯定会更清晰,但似乎对于一个SO答案来说可能太长了(尤其是考虑到我的冗长)。这可能会成为一篇有趣的博客文章——但如果没有一些未绑定超级的用例,仍然很难激发兴趣。至于它的目的,我猜想它可能只是因为在构建Python 2.2的描述符和超级后实现起来很简单,所以Guido只是实现了它,而不必担心是否有用例。这在现代Python中可能不会发生,但当时情况不同。 - abarnert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接