Reputation: 5850
Won’t super(cls, instance)
and super(cls, subclass)
both return the superclass of cls
?
Upvotes: 3
Views: 1620
Reputation: 8047
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'
Upvotes: 0
Reputation: 1123480
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.
Upvotes: 11