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?
A super() Deep Dive While the examples above (and below) call super() without any parameters, super() can also take two parameters: the first is the subclass, and the second parameter is an object that is an instance of that subclass.
The super() method does not accept any arguments. You specify the method you want to inherit after the super() method. The above Cheese Python class inherits the values from the Food class __init__() method. We call the super() class method to inherit values from the Food class.
When you initialize a child class in Python, you can call the super(). __init__() method. This initializes the parent class object into the child class. In addition to this, you can add child-specific information to the child object as well.
super(). __init__(**kwargs): expands them into keyword arguments again.
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()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With