29

While reading the Python documentation on super(), I stumbled on the following statement:

If the second argument is omitted, the super object returned is unbound.

What does ‘unbound’ mean and how to use super() with one argument?

Géry Ogam
  • 6,336
  • 4
  • 38
  • 67
user3282758
  • 1,379
  • 11
  • 29
  • 1
    See [Class method differences in Python: bound, unbound and static](http://stackoverflow.com/q/114214) for what it means for something to be bound or unbound. – Martijn Pieters May 12 '15 at 11:59

2 Answers2

35

Python function objects are descriptors, and Python uses the descriptor protocol to bind functions to an instance. This process produces a bound method.

Binding is what makes the 'magic' self argument appear when you call a method, and what makes a property object automatically call methods when you try to use the property as an attribute on instances.

super() with two arguments invokes the same descriptor protocol when you try to use it to look up methods on parent classes; super(Foo, self).bar() will traverse the Foo parent classes until an attribute bar is found, and if that is an object that is a descriptor, it will be bound to self. Calling bar then calls the bound method, which in turn calls the function passing in the self argument as bar(self).

To do this, the super() object stores both the class (first argument) and self (second argument) to bind with, and the type of the self argument as the attributes __thisclass__, __self__ and __self_class__ respectively:

>>> class Foo:
...     def bar(self):
...         return 'bar on Foo'
... 
>>> class Spam(Foo):
...     def bar(self):
...         return 'bar on Spam'
... 
>>> spam = Spam()
>>> super(Spam, spam)
<super: <class 'Spam'>, <Spam object>>
>>> super(Spam, spam).__thisclass__
<class '__main__.Spam'>
>>> super(Spam, spam).__self__
<__main__.Spam object at 0x107195c10>
>>> super(Spam, spam).__self_class__
<class '__main__.Spam'>

When looking up attributes, the __mro__ attribute of the __self_class__ attribute is searched, starting one position past the position of __thisclass__, and the results are bound.

super() with just one argument will have its __self__ and __self_class__ attributes set to None and cannot do lookups yet:

>>> super(Spam)
<super: <class 'Spam'>, NULL>
>>> super(Spam).__self__ is None
True
>>> super(Spam).__self_class__ is None
True
>>> super(Spam).bar
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'super' object has no attribute 'bar'

The object does support the descriptor protocol, so you can bind it just like you can bind a method:

>>> super(Spam).__get__(spam, Spam)
<super: <class 'Spam'>, <Spam object>>
>>> super(Spam).__get__(spam, Spam).bar()
'bar on Foo'

This means you can store such an object on a class and use it to traverse to parent methods:

>>> class Eggs(Spam):
...     pass
... 
>>> Eggs.parent = super(Eggs)
>>> eggs = Eggs()
>>> eggs.parent
<super: <class 'Eggs'>, <Eggs object>>
>>> eggs.parent.bar()
'bar on Spam'

The primary use case would be to avoid having to repeat the class each time with the two-argument form of super():

class Foo:
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        # class-private attribute so subclasses don’t clobber one another
        setattr(cls, f'_{cls.__name__}__parent', super(cls))

    def bar(self):
        return 'bar on Foo'

class Spam(Foo):
    def bar(self):
        return 'spammed: ' + self.__parent.bar()

but that breaks when using a class method (since cls.__parent won’t bind) and has been superseded by Python 3’s super() with zero arguments which picks up the class from a closure:

class Foo:
    def bar(self):
        return 'bar on Foo'

class Spam(Foo):
    def bar(self):
        return 'spammed: ' + super().bar()
Géry Ogam
  • 6,336
  • 4
  • 38
  • 67
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    Extremely useful answer as this behavior is undocumented, thank you Martijn. – Géry Ogam Jun 01 '19 at 21:30
  • Is there a built in way to traverse the MRO of the class and return unbounded results? – Robin De Schepper Jun 10 '20 at 11:20
  • @RobinDeSchepper: just iterate over the [class `__mro__` attribute](https://docs.python.org/3/library/stdtypes.html?highlight=__mro__#class.__mro__), then access the class namespace directly (e.g. [`vars(classobject)`](https://docs.python.org/3/library/functions.html#vars) gives you that namespace, a dictionary). – Martijn Pieters Jun 11 '20 at 11:12
  • Great answer! But what is a practical usage of `super` with only one argument? I have `Bar` class with `bar = 'bar'` class attribute and `super(Bar) .bar` giving `AttributeError`. – Давид Шико Dec 29 '22 at 21:47
  • 1
    @ДавидШико read the whole answer, I cover that explicitly – Martijn Pieters Dec 29 '22 at 23:39
0

In addition, you can also do this:

>>> class Eggs(Spam):
...     grand_parent = super(Spam)
... 
>>> eggs = Eggs()
>>> eggs.grand_parent.bar()
'bar on Foo'

I discovered this because I wanted to avoid creating class attribute outside of the class, and I was unable to do this:

>>> class Eggs(Spam):
...     parent = super(Eggs)
... 
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "<stdin>", line 2, in Eggs
NameError: name 'Eggs' is not defined

However, you can use instance attribute to achieve it:

>>> class Eggs(Spam):
...     def __init__(self):
...         self.Parent = super(Eggs).__get__(self, Eggs)
...
>>> eggs = Eggs()
>>> eggs.parent.bar()
'bar on Spam'
Vanbliser
  • 1
  • 1