super(cls, 实例) 和 super(cls, 子类) 的区别是什么?
难道 super(cls, instance)
和 super(cls, subclass)
都不会返回 cls
的父类吗?
2 个回答
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).attr
和 super(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'
这两者之间的区别非常大;使用 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 中,这种用法已经完全过时,因此它被计划废弃。