سؤال

Won’t super(cls, instance) and super(cls, subclass) both return the superclass of cls?

هل كانت مفيدة؟

المحلول

The difference is huge; super() with a type (class) second argument instead of an object (instance) gives you unbound methods, not bound methods (just like accessing those methods on a class would).

I'll explain first how super() works with an instance second argument.

super() inspects the MRO of self, finds the first argument (type or supertype) in the MRO, then finds the next object that has the requested attribute.

Demo:

>>> 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'>)

The MRO of Derived is (Derived, Intermediary, BaseClass); super() finds this MRO by looking at the second argument, using type(d).__mro__. The search for foo starts at the next class after the first argument given.

The foo() method is bound here, you can just call it.

If you give super() a type as the second argument, then it'll use the MRO of that type, e.g. instead of using type(instance).__mro__ it just goes for type.__mro__. However it then has no instance to bind the methods to. super(supertype, type).foo is just the (unbound) function object:

>>> 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'

To call .foo() I have to explicitly pass in a self argument.

(In Python 2, the above would return a foo unbound method object instead of a function, but the principle is the same).

The method returned is also, again, from the next class in the MRO chain; BaseClass.foo was returned there.

This is down to the function.__get__ method (i.e. the descriptor protocol, responsible for binding), as it returns itself (or, in Python 2, an unbound method) when passed a class to bind to. (For classmethod objects, __get__ does return a bound object when passed in a class).

So, TL;DR, for methods super(type, object) returns a bound method, super(supertype, type) returns unbound methods. The goal was to make it possible to store this object as a class-private attribute to avoid having to keep looking up the class object, see How to use super() with one argument?. It's use-case has been obsoleted entirely in Python 3 so it is slated for deprecation.

نصائح أخرى

super(cls, instance).attr inspects the M.R.O. of the class of instance (i.e. instance.__class__.__mro__), looks up the next class after cls in the M.R.O. that has an attribute attr, and returns the result of attr.__get__(instance, instance.__class__) if it has a __get__ method or returns attr if it has no __get__ method. This case is used in functions:

>>> 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 inspects the M.R.O. of subclass (i.e. subclass.__mro__), looks up the next class after cls in the M.R.O. that has an attribute attr, and returns the result of attr.__get__(None, subclass) if it has a __get__ method or returns attr if it has no __get__ method. This case is used in 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'

For function attributes, super(cls, instance).attr returns a bound method of instance, while super(cls, subclass).attr returns a function:

>>> 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'

For classmethod attributes, super(cls, instance).attr and super(cls, subclass).attr return a bound method of respectively instance.__class__ and 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'
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top