super(cls, 实例) 和 super(cls, 子类) 的区别是什么?

3 投票
2 回答
1484 浏览
提问于 2025-04-17 22:17

难道 super(cls, instance)super(cls, subclass) 都不会返回 cls 的父类吗?

2 个回答

0

super(cls, instance).attr 这个表达式的意思是,它会检查 instance 这个对象的类的继承顺序(也就是 instance.__class__.__mro__),然后找到在这个顺序中,紧跟在 cls 后面,并且有 attr 这个属性的下一个类。如果找到了这个属性,并且它有一个 __get__ 方法,那么就会返回 attr.__get__(instance, instance.__class__) 的结果;如果没有这个方法,就直接返回 attr。这个情况通常用在 函数 中:

>>> class A:
...     def f(self): return 'A.f'
... 
>>> class B(A):
...     def f(self): return 'B.f ' + super(B, self).f()
... 
>>> B().f()
'B.f A.f'

super(cls, subclass).attr 这个表达式也是类似的,它会检查 subclass 的继承顺序(也就是 subclass.__mro__),然后找到在这个顺序中,紧跟在 cls 后面,并且有 attr 这个属性的下一个类。如果找到了这个属性,并且它有一个 __get__ 方法,就会返回 attr.__get__(None, subclass) 的结果;如果没有这个方法,就直接返回 attr。这个情况通常用在 classmethod 中:

>>> class A:
...     @classmethod
...     def f(cls): return 'A.f'
... 
>>> class B(A):
...     @classmethod
...     def f(cls): return 'B.f ' + super(B, cls).f()
... 
>>> B.f()
'B.f A.f'

对于函数属性来说,super(cls, instance).attr 返回的是 instance 的一个 绑定方法,而 super(cls, subclass).attr 返回的是一个 函数

>>> class A:
...     def f(self): return 'A.f'
... 
>>> class B(A):
...     def f(self): return 'B.f'
... 
>>> b = B()
>>> b.f
<bound method B.f of <__main__.B object at 0x10e7d3fa0>>
>>> B.f
<function B.f at 0x10e7ea790>
>>> b.f()
'B.f'
>>> B.f(b)
'B.f'
>>> super(B, b).f
<bound method A.f of <__main__.B object at 0x10e7d3fa0>>
>>> super(B, B).f
<function A.f at 0x10e7ea700>
>>> super(B, b).f()
'A.f'
>>> super(B, B).f(b)
'A.f'

对于 classmethod 属性,super(cls, instance).attrsuper(cls, subclass).attr 分别返回的是 instance.__class__subclass绑定方法

>>> class A:
...     @classmethod
...     def f(cls): return 'A.f'
... 
>>> class B(A):
...     @classmethod
...     def f(cls): return 'B.f'
... 
>>> b = B()
>>> b.f
<bound method B.f of <class '__main__.B'>>
>>> B.f
<bound method B.f of <class '__main__.B'>>
>>> b.f()
'B.f'
>>> B.f()
'B.f'
>>> super(B, b).f
<bound method A.f of <class '__main__.B'>>
>>> super(B, B).f
<bound method A.f of <class '__main__.B'>>
>>> super(B, b).f()
'A.f'
>>> super(B, B).f()
'A.f'
11

这两者之间的区别非常大;使用 super() 时,如果第二个参数是一个类型(类),而不是一个对象(实例),那么你得到的是未绑定的方法,而不是绑定的方法(就像直接在类上访问这些方法一样)。

我先来解释一下 super() 在实例作为第二个参数时是如何工作的。

super() 会查看 self 的方法解析顺序(MRO),找到第一个参数(类型或超类)在 MRO 中的位置,然后寻找下一个拥有所请求属性的对象。

演示:

>>> class BaseClass(object):
...     def foo(self): return 'BaseClass foo'
... 
>>> class Intermediary(BaseClass):
...     def foo(self): return 'Intermediary foo'
... 
>>> class Derived(Intermediary):
...     def foo(self): return 'Derived foo'
... 
>>> d = Derived()
>>> d.foo()
'Derived foo'
>>> super(Derived, d).foo
<bound method Intermediary.foo of <__main__.Derived object at 0x10ef4de90>>
>>> super(Derived, d).foo()
'Intermediary foo'
>>> super(Intermediary, d).foo()
'BaseClass foo'
>>> Derived.__mro__
(<class '__main__.Derived'>, <class '__main__.Intermediary'>, <class '__main__.BaseClass'>, <type 'object'>)

Derived 的 MRO 是 (Derived, Intermediary, BaseClass)super() 通过查看第二个参数来找到这个 MRO,使用 type(d).__mro__。对 foo 的搜索从给定的第一个参数之后的下一个类开始。

在这里,foo() 方法是绑定的,你可以直接调用它。

如果你给 super() 一个类型作为第二个参数,那么它会使用那个类型的 MRO,比如说,它不再使用 type(instance).__mro__,而是直接用 type.__mro__。但是这样就没有实例可以将方法绑定到。super(supertype, type).foo 只是一个未绑定的函数对象

>>> super(Intermediary, Derived).foo
<function BaseClass.foo at 0x106dd6040>
>>> super(Intermediary, Derived).foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() missing 1 required positional argument: 'self'
>>> super(Intermediary, Derived).foo(d)
'BaseClass foo'

要调用 .foo(),我必须明确传入一个 self 参数。

(在 Python 2 中,上面的代码会返回一个 foo未绑定方法 对象,而不是一个函数,但原理是一样的)。

返回的方法也是来自 MRO 链中的下一个类;在这里返回的是 BaseClass.foo

这与 function.__get__ 方法有关(即 描述符协议,负责绑定),当传入一个类进行绑定时,它会返回自身(或者在 Python 2 中返回一个未绑定的方法)。对于 classmethod 对象__get__ 在传入一个类时会返回一个绑定对象。

所以,总结一下,对于方法来说,super(type, object) 返回的是一个 绑定 方法,而 super(supertype, type) 返回的是未绑定的方法。这样做的目的是为了能够将这个对象存储为类的私有属性,以避免不断查找类对象,具体可以参考 如何在 Python 中使用 super() 只传一个参数?。在 Python 3 中,这种用法已经完全过时,因此它被计划废弃

撰写回答