关于python:为什么classmethod的super需要第二个参数?

Why does a classmethod's super need a second argument?

按预期工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> class Foo(object):
...   @classmethod
...   def hello(cls):
...     print 'hello, foo'
...
>>> class Bar(Foo):
...   @classmethod
...   def hello(cls):
...     print 'hello, bar'
...     super(Bar, cls).hello()
...
>>> b = Bar()
>>> b.hello()
hello, bar
hello, foo

我也可以显式调用基类:

1
2
3
4
5
6
7
8
9
10
>>> class Bar(Foo):
...   @classmethod
...   def hello(cls):
...     print 'hello, bar'
...     Foo.hello()
...
>>> b = Bar()
>>> b.hello()
hello, bar
hello, foo

我想知道为什么我不能像这样省略对super的第一个论点:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> class Bar(Foo):
...   @classmethod
...   def hello(cls):
...     print 'hello, bar'
...     super(Bar).hello()
...
>>> b = Bar()
>>> b.hello()
hello, bar
Traceback (most recent call last):
  File"<stdin>", line 1, in <module>
  File"<stdin>", line 5, in hello
AttributeError: 'super' object has no attribute 'hello'

如果没有第二个参数的super调用的结果似乎是超类型内的类类型:

1
2
3
4
5
6
7
8
9
10
11
12
>>> class Bar(Foo):
...   @classmethod
...   def hello(cls):
...     print Foo, type(Foo)
...     print super(Bar), type(super(Bar))
...     print cls, type(cls)
...
>>> b = Bar()
>>> b.hello()
<class '__main__.Foo'> <type 'type'>
<super: <class 'Bar'>, NULL> <type 'super'>
<class '__main__.Bar'> <type 'type'>

我想我只是想知道这里的设计。为什么需要将类对象传递到super调用中以获取对基类类型Foo的引用?对于普通方法,将self传递给函数是有意义的,因为它需要将基类类型绑定到类的实际实例。但是类方法不需要类的特定实例。

编辑:我在python 3.2中得到的错误与在2.7中为super(Bar).hello()所做的相同。不过,我可以简单地做super().hello(),这很好。


super()返回描述符,需要两项:

  • 搜索类层次结构的起点。
  • 绑定返回方法的参数。

对于两个参数(和隐式零参数*)的情况,第二个参数用于绑定到,但如果不传入第二个参数,super()无法调用描述符协议来绑定返回的函数、类方法、属性或其他描述符。classmethods仍然是描述符并且是绑定的;绑定到类而不是实例,但是super()不知道描述符将如何使用绑定到的上下文。

super()不应该也不可能知道您在查找类方法而不是常规方法;类方法只与常规方法不同,因为它们的.__get__()方法的行为不同。

为什么类方法是绑定的?因为当您子类Foo但不重写.hello()时,调用Bar.hello()调用Foo.__dict__['hello']函数,将其绑定到Bar上,您对hello(cls)的第一个论点将是该子类,而不是Foo子类。

没有第二个参数,super()返回一个未绑定的对象,稍后可以手动绑定该对象。您可以使用super()实例提供的.__get__()方法进行绑定:

1
2
3
4
5
class Bar(Foo):
    @classmethod
    def hello(cls):
        print 'hello, bar'
        super(Bar).__get__(cls, None).hello()

在没有上下文的实例上,super().__get__()有效地返回一个具有上下文集的新super()实例。在一个上下文为.__get__()的实例上,只返回self;它已经绑定。

*在python 3中,如果不从绑定方法内部调用super(),将使用调用帧隐式地发现类型和绑定对象是什么,因此在这种情况下,您不再需要显式地传递类型和对象参数。为了达到这个目的,python 3实际上在方法中添加了一个隐式的__class__闭包变量。参见pep 3135,为什么python 3.x的super()有魔力?