【问题标题】:Using `super()` within `__init_subclass__` doesn't find parent's classmethod [duplicate]在`__init_subclass__`中使用`super()`找不到父类方法[重复]
【发布时间】:2018-04-18 00:07:03
【问题描述】:

我尝试从__init_subclass__ 中访问父级的类方法,但这似乎不起作用。 假设如下示例代码:

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 方法。
  • 你为什么在这里使用super(cls)?未绑定的超级很少有用,而且它们不是您要在这里寻找的。如果您只是想调用clsfoo 类方法的父类,那就是super(cls, cls).foo(),就像您使用方法的实例一样。如果你想做一些不同的事情……那又怎样?
  • 如果您真的想了解为什么这并不能如您所愿,您需要先了解一些背景知识,然后才能对其进行解释。你知道什么是描述符,方法一般是如何工作的,超级一般是如何工作的,并且只需要知道未绑定的超级是如何工作的吗?还是您需要解释这些更基本的部分之一?
  • @abarnert 诚然,我以前从未使用过 unbound super 并且可能被 the docs 弄糊涂了:super([type[, object-or-type]]) 返回一个代理对象,它将方法调用委托给类型的父类或同级类。 [...]。所以我可能需要对此进行复习;其余的我都熟悉。但最后你提到的还是有道理的。

标签: python inheritance python-3.6 class-method


【解决方案1】:

一个普通的super 对象(您通常通过调用super(MyType, self)super()super(MyType, myobj) 获得)跟踪类型和创建它的对象。每当您在 super 上查找一个属性时,它会在方法解析顺序中跳过 MyType,但如果它找到一个方法,它会将其绑定到该 self 对象。

一个未绑定的super 没有没有self 对象。所以,super(cls) 跳过 MRO 中的 cls 找到方法 foo,然后将其绑定到……哎呀,它没有什么可调用的。

那么,您可以在哪些内容上调用classmethod?类本身,或它的子类,或该类或子类的实例。因此,其中任何一个都可以作为super 的第二个参数,最明显的是:

super(cls, cls)

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


如果您想知道为什么未绑定的super 不起作用,您必须了解未绑定的super 到底是什么。不幸的是,the docs 中唯一的解释是:

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

这是什么意思?好吧,您可以尝试从第一原则中将其与未绑定方法的含义平行(当然,在现代 Python 中未绑定方法不是一个东西),或者您可以阅读 @987654322 @,或者原来的introduction to 2.2's class-type unification (including a pure-Python super clone)

super 对象具有__self__ 属性,就像方法对象一样。而super(cls) 缺少它的__self__,就像str.split 一样。1

您不能像使用未绑定方法那样显式使用未绑定的super(例如,str.split('123', '2')'123'.split('2') 的作用相同,但 super(cls).foo(cls) 则不然与super(cls, cls).foo() 工作相同)。但是您可以隐式地使用它们,就像您一直使用未绑定方法一样,无需通常考虑它。

如果你不知道how methods work,那么tl'dr 是:当你评估myobj.mymeth 时,Python 查找mymeth,在myobj 本身上找不到它,但在myobj 上找到它类型,所以它检查它是否是一个non-data descriptor,如果是,调用它的__get__方法将它绑定到myobj

所以,未绑定方法2 是非数据描述符,其__get__ 方法返回绑定方法。未绑定的@classmethods 类似,但它们的__get__ 忽略了对象并返回绑定到类的绑定方法。等等。

未绑定的supers 是非数据描述符,其__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.fA.f,就像super().f 会在里面一样B 方法。

您为什么要这样做?我不确定。我用 Python 编写了各种可笑的动态和反射代码(例如,用于其他解释器的透明代理),我不记得曾经需要未绑定的 super。但是,如果您需要它,它就在那里。


1。我在这里有点作弊。首先,未绑定方法在 Python 3 中不再存在——但函数的工作方式相同,因此 Python 在过去使用未绑定方法的地方使用它们。其次,str.split,作为一个 C 内置函数,即使在 2.x 中也不是一个合适的未绑定方法——但无论如何它的行为就像一个,至少就我们在这里所关心的而言。

2。实际上是普通的旧函数。

【讨论】:

  • 感谢您的详尽解释。尤其是您的最后一条评论“未绑定的超级是非数据描述符,其__get__ 方法返回绑定的超级。” 让它点击。
  • @a_guest 是的,这确实是关键——“未绑定的X”表示“X.__get__ 返回绑定的 X”。除了没有在任何地方说明一般规则,并且它的唯一特殊情况被记录在任何地方 甚至不再存在于 Python 3 中,所以......不是最容易发现的规则。顺便说一句,我认为应该有更好的方法来组织这个答案,这样它会帮助更多的人,但我想不出它是什么;如果您有任何想法,那就太好了。
  • 也许这是一个关于绑定和非绑定超级之间相互作用的更普遍的问题?有this SO question 关于未绑定的超级,但答案完全没有;没有其他 SO 资源真正处理该主题。但总的来说,我想我会从解释 MRO 和激励(绑定)超级的使用开始,然后切换到未绑定超级,解释它是如何创建的,它是什么(连同描述符),转换为绑定超级,它的目的是什么和一些用例(尽管这些似乎很难找到)。
  • @a_guest 那肯定会更清楚,但对于 SO 答案来说似乎太长了(尤其是我的冗长)。它可能会成为一篇有趣的博客文章——但如果没有一些未绑定超级的用例,它仍然很难激励。至于它的目的,我怀疑它可能就在那里,因为一旦你为 Python 2.2 构建了描述符和超级,它就很容易实现,所以 Guido 只是实现了它,而不用担心是否有用例。这在现代 Python 中可能不会发生,但当时情况不同。
猜你喜欢
  • 2012-08-09
  • 2019-02-02
  • 1970-01-01
  • 1970-01-01
  • 2021-05-06
  • 1970-01-01
  • 2023-04-06
  • 1970-01-01
  • 2020-03-03
相关资源
最近更新 更多