为什么Python 3.x中的super()是魔术方法?

Python 3。x, super()可以不带参数调用:

1
2
3
4
5
6
7
class A(object):
    def x(self):
         print("Hey now")

class B(A):
    def x(self):
        super().x()
1
2
>>> B().x()
Hey now

为了实现这一功能,执行了一些编译时魔法,其结果之一是以下代码(将super重新绑定到super_)失败:

1
2
3
4
5
6
7
8
9
super_ = super

class A(object):
    def x(self):
        print("No flipping")

class B(A):
    def x(self):
        super_().x()
1
2
3
4
5
>>> B().x()
Traceback (most recent call last):
  File"<stdin>", line 1, in <module>
  File"<stdin>", line 3, in x
RuntimeError: super(): __class__ cell not found

为什么super()在没有编译器的帮助下无法在运行时解析超类?在实际情况中,这种行为或其根本原因是否会影响粗心的程序员?

…还有一个附带的问题:Python中是否还有其他函数、方法等的例子可以通过将它们重新绑定到不同的名称来打破它们?


添加新的magic super()行为是为了避免违反D.R.Y.(不要重复自己)原则,见PEP 3135。必须显式地将类引用为全局类来命名也容易出现与super()本身相同的重新绑定问题:

1
2
3
4
5
6
7
8
class Foo(Bar):
    def baz(self):
        return super(Foo, self).baz() + 42

Spam = Foo
Foo = something_else()

Spam().baz()  # liable to blow up

同样的情况也适用于使用类修饰器,修饰器返回一个新对象,该对象将重新绑定类名:

1
2
3
4
5
@class_decorator_returning_new_class
class Foo(Bar):
    def baz(self):
        # Now `Foo` is a *different class*
        return super(Foo, self).baz() + 42

神奇的super() __class__单元格通过让您访问原始类对象很好地回避了这些问题。

PEP由Guido启动,他最初设想super成为一个关键字,使用单元格查找当前类的想法也是他的想法。当然,使其成为关键字的想法是PEP初稿的一部分。

然而,实际上是圭多自己放弃了关键字"太神奇"的想法,转而提出了当前的实现。他预计为super()使用不同的名称可能是一个问题:

My patch uses an intermediate solution: it assumes you need __class__
whenever you use a variable named 'super'. Thus, if you (globally)
rename super to supper and use supper but not super, it won't work
without arguments (but it will still work if you pass it either
__class__ or the actual class object); if you have an unrelated
variable named super, things will work but the method will use the
slightly slower call path used for cell variables.

因此,最后是圭多本人宣布,使用super关键字感觉不对,提供一个神奇的__class__单元格是可以接受的折衷办法。

我同意实现的神奇的、隐式的行为有点令人惊讶,但是super()是该语言中应用最广泛的函数之一。只要看看所有误用的super(type(self), self)super(self.__class__, self)调用在互联网上发现;如果这些代码中有任何一个是从派生类中调用的,那么最终会出现无限递归异常。至少,没有参数的简化super()调用避免了这个问题。

重新命名的super_;只要在你的方法中引用__class__,它就会再次运行。如果在方法中引用super__class__名称,则创建单元格:

1
2
3
4
5
6
7
8
9
10
11
12
>>> super_ = super
>>> class A(object):
...     def x(self):
...         print("No flipping")
...
>>> class B(A):
...     def x(self):
...         __class__  # just referencing it is enough
...         super_().x()
...
>>> B().x()
No flipping