在一般情况下,Python 的 super() 实际上是如何工作的?
How does Python's super() actually work, in the general case?
super()
上有很多很棒的资源,包括经常弹出的 this 很棒的博客 post,以及 Stack Overflow 上的许多问题。但是我觉得他们都没有解释它在最一般的情况下是如何工作的(使用任意继承图),以及幕后发生的事情。
考虑钻石继承的这个基本示例:
class A(object):
def foo(self):
print 'A foo'
class B(A):
def foo(self):
print 'B foo before'
super(B, self).foo()
print 'B foo after'
class C(A):
def foo(self):
print 'C foo before'
super(C, self).foo()
print 'C foo after'
class D(B, C):
def foo(self):
print 'D foo before'
super(D, self).foo()
print 'D foo after'
如果您从 this or look up the wikipedia page 等来源阅读 Python 的 C3 线性化方法解析顺序规则,您会发现 MRO 必须是 (D, B, C, A, object)
。这当然得到了D.__mro__
:
的证实
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
和
d = D()
d.foo()
打印
D foo before
B foo before
C foo before
A foo
C foo after
B foo after
D foo after
与 MRO 匹配。但是,请考虑上面 super(B, self).foo()
在 B
中实际上调用 C.foo
,而在 b = B(); b.foo()
中它会直接转到 A.foo
。很明显,使用 super(B, self).foo()
并不像有时教导的那样只是 A.foo(self)
的快捷方式。
super()
显然知道之前的调用以及链试图遵循的整体 MRO。我可以看到有两种方法可以实现这一点。第一个是做一些事情,比如将 super
对象本身作为 self
参数传递给链中的下一个方法,它的行为与原始对象一样,但也包含此信息。然而,这似乎也会破坏很多东西(super(D, d) is d
是错误的)并且通过做一些实验我可以看到情况并非如此。
另一种选择是使用某种全局上下文来存储 MRO 和其中的当前位置。我想象 super
的算法是这样的:
- 目前有我们正在工作的环境吗?如果没有,请创建一个包含队列的队列。获取 class 参数的 MRO,将除第一个元素之外的所有元素推入队列。
- 从当前上下文的 MRO 队列中弹出下一个元素,在构造
super
实例时将其用作当前 class。
- 当从
super
实例访问方法时,在当前 class 中查找它并使用相同的上下文调用它。
但是,这并没有说明奇怪的事情,例如使用不同的基数 class 作为调用 super
的第一个参数,甚至在其上调用不同的方法。我想知道这个的一般算法。另外,如果这个上下文存在于某处,我可以检查它吗?我可以搞砸吗?这当然是个糟糕的想法,但 Python 通常希望你成为一个成熟的成年人,即使你不是。
这也引入了很多设计注意事项。如果我写 B
只考虑它与 A
的关系,那么后来别人写 C
,第三个人写 D
,我的 B.foo()
方法必须以与 C.foo()
兼容的方式调用 super
,即使在我编写它时它不存在!如果我希望我的 class 易于扩展,我将需要考虑到这一点,但我不确定它是否比简单地确保 foo
的所有版本都具有相同的签名更复杂。还有一个问题是什么时候在调用 super
之前或之后放置代码,即使仅考虑 B
的基础 classes 没有任何区别。
super() is then obviously aware of the previous calls before it
不是。当您执行 super(B, self).foo
时,super
知道 MRO,因为那只是 type(self).__mro__
,并且它知道它应该在 [= 之后立即在 MRO 中的那个点开始寻找 foo
15=]。粗略的纯Python等价物是
class super(object):
def __init__(self, klass, obj):
self.klass = klass
self.obj = obj
def __getattr__(self, attrname):
classes = iter(type(self.obj).__mro__)
# search the MRO to find self.klass
for klass in classes:
if klass is self.klass:
break
# start searching for attrname at the next class after self.klass
for klass in classes:
if attrname in klass.__dict__:
attr = klass.__dict__[attrname]
break
else:
raise AttributeError
# handle methods and other descriptors
try:
return attr.__get__(self.obj, type(self.obj))
except AttributeError:
return attr
If I wrote B thinking only of its relation to A, then later someone else writes C and a third person writes D, my B.foo() method has to call super in a way that is compatible with C.foo() even though it didn't exist at the time I wrote it!
没有人期望您能够从任意 类 进行多重继承。除非 foo
专门设计为在多重继承情况下被兄弟 类 重载,否则 D 不应该存在。
super()
上有很多很棒的资源,包括经常弹出的 this 很棒的博客 post,以及 Stack Overflow 上的许多问题。但是我觉得他们都没有解释它在最一般的情况下是如何工作的(使用任意继承图),以及幕后发生的事情。
考虑钻石继承的这个基本示例:
class A(object):
def foo(self):
print 'A foo'
class B(A):
def foo(self):
print 'B foo before'
super(B, self).foo()
print 'B foo after'
class C(A):
def foo(self):
print 'C foo before'
super(C, self).foo()
print 'C foo after'
class D(B, C):
def foo(self):
print 'D foo before'
super(D, self).foo()
print 'D foo after'
如果您从 this or look up the wikipedia page 等来源阅读 Python 的 C3 线性化方法解析顺序规则,您会发现 MRO 必须是 (D, B, C, A, object)
。这当然得到了D.__mro__
:
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
和
d = D()
d.foo()
打印
D foo before
B foo before
C foo before
A foo
C foo after
B foo after
D foo after
与 MRO 匹配。但是,请考虑上面 super(B, self).foo()
在 B
中实际上调用 C.foo
,而在 b = B(); b.foo()
中它会直接转到 A.foo
。很明显,使用 super(B, self).foo()
并不像有时教导的那样只是 A.foo(self)
的快捷方式。
super()
显然知道之前的调用以及链试图遵循的整体 MRO。我可以看到有两种方法可以实现这一点。第一个是做一些事情,比如将 super
对象本身作为 self
参数传递给链中的下一个方法,它的行为与原始对象一样,但也包含此信息。然而,这似乎也会破坏很多东西(super(D, d) is d
是错误的)并且通过做一些实验我可以看到情况并非如此。
另一种选择是使用某种全局上下文来存储 MRO 和其中的当前位置。我想象 super
的算法是这样的:
- 目前有我们正在工作的环境吗?如果没有,请创建一个包含队列的队列。获取 class 参数的 MRO,将除第一个元素之外的所有元素推入队列。
- 从当前上下文的 MRO 队列中弹出下一个元素,在构造
super
实例时将其用作当前 class。 - 当从
super
实例访问方法时,在当前 class 中查找它并使用相同的上下文调用它。
但是,这并没有说明奇怪的事情,例如使用不同的基数 class 作为调用 super
的第一个参数,甚至在其上调用不同的方法。我想知道这个的一般算法。另外,如果这个上下文存在于某处,我可以检查它吗?我可以搞砸吗?这当然是个糟糕的想法,但 Python 通常希望你成为一个成熟的成年人,即使你不是。
这也引入了很多设计注意事项。如果我写 B
只考虑它与 A
的关系,那么后来别人写 C
,第三个人写 D
,我的 B.foo()
方法必须以与 C.foo()
兼容的方式调用 super
,即使在我编写它时它不存在!如果我希望我的 class 易于扩展,我将需要考虑到这一点,但我不确定它是否比简单地确保 foo
的所有版本都具有相同的签名更复杂。还有一个问题是什么时候在调用 super
之前或之后放置代码,即使仅考虑 B
的基础 classes 没有任何区别。
super() is then obviously aware of the previous calls before it
不是。当您执行 super(B, self).foo
时,super
知道 MRO,因为那只是 type(self).__mro__
,并且它知道它应该在 [= 之后立即在 MRO 中的那个点开始寻找 foo
15=]。粗略的纯Python等价物是
class super(object):
def __init__(self, klass, obj):
self.klass = klass
self.obj = obj
def __getattr__(self, attrname):
classes = iter(type(self.obj).__mro__)
# search the MRO to find self.klass
for klass in classes:
if klass is self.klass:
break
# start searching for attrname at the next class after self.klass
for klass in classes:
if attrname in klass.__dict__:
attr = klass.__dict__[attrname]
break
else:
raise AttributeError
# handle methods and other descriptors
try:
return attr.__get__(self.obj, type(self.obj))
except AttributeError:
return attr
If I wrote B thinking only of its relation to A, then later someone else writes C and a third person writes D, my B.foo() method has to call super in a way that is compatible with C.foo() even though it didn't exist at the time I wrote it!
没有人期望您能够从任意 类 进行多重继承。除非 foo
专门设计为在多重继承情况下被兄弟 类 重载,否则 D 不应该存在。