3

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

Géry Ogam
  • 6,336
  • 4
  • 38
  • 67
dspjm
  • 5,473
  • 6
  • 41
  • 62

2 Answers2

11

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.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • I believe you but I did not find any information about this on official documents. Is there any official explain about this? – Kramer Li Jul 17 '17 at 09:12
  • @KramerLi: It's all covered in the [`super()` documentation](https://docs.python.org/3/library/functions.html#super). – Martijn Pieters Jul 17 '17 at 09:19
  • @KramerLi: I recently 'ported' the implementation to Python to answer another question: [What is the type of the super object returned by super()?](//stackoverflow.com/a/44994572). Would that help in understanding? – Martijn Pieters Jul 17 '17 at 09:49
  • Thanks, your answer here already very clear. The most important info to me from your answer is that "super() with a type (class) second argument instead of an object (instance) gives you unbound methods". I think that this is a very important info that the python official documents should mention. But I really did not find this kind of information on the official documents(I also read the link you provided). So I am just wondering did I missed anything from the official doc ? – Kramer Li Jul 17 '17 at 11:25
  • @KramerLi: I perhaps put more emphasis on that than needed, but I do see the documentation is not as clear on that the same binding rules apply as normal instance and class attribute access. – Martijn Pieters Jul 17 '17 at 11:28
  • @Maggyero: sorry, but I had to roll back those edits. You changed huge swathes of my answer, and I spent a lot of headscratching trying to find a reference I knew was around somewhere but was removed by your edit. I don’t really have time to review right now if there is anything I want to salvage. Please don’t edit my posts to this extent. – Martijn Pieters May 07 '21 at 22:41
  • Yes my multiple edits ended up in a big change, so I should have posted a separate answer. That is what I did eventually. – Géry Ogam May 08 '21 at 00:02
0

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'
Géry Ogam
  • 6,336
  • 4
  • 38
  • 67