This function gives you access to methods in a superclass from the subclass that inherits from it.
Return a proxy/temporary object that allows to call superclass’s methods.
This is useful for accessing inherited methods that have been overridden in a class. The search order is same as that used by getattr() except that the type itself is skipped.
Usage of super()
But first of all let's check what is bound/unbound method
Method bound to the object is the function that passes that object as the first argument (instance for instance methods, class for class methods).
a =A()a.method(x) --> A.method(a, x)# <-- here a.method is bound method
"bound_method is bound method :)
super() will return bound method depends from context. So for method like __new__ (which has class as first argument it will return unbound class method).
In the following example notice that we pass cls in __new__() but in __init__ we don't pass self. This is because in case of __init__ this method is bound to self instance already.
Usage of super() with different types of methods
Please notice that we don't pass cls/self into class/instance methods:
classA: attr ="class attr from A"definstance_method(self):print("Running method of A")return self.attr@classmethoddefclass_method(cls):return cls.attr@staticmethoddefstatic_method():return"Some static data (from A class)"classC:definstance_method(self):return"I AM FROM C"classB(A,C): attr ="class attr from B"definstance_method(self):print("Running instance method in B...")#return A.instance_method(self)returnsuper().instance_method()# FROM A (next in MRO)# return super(B, self).instance_method() # FROM A (next in MRO)#return super(A, self).instance_method() # FROM C (after A)@classmethoddefclass_method(cls):print("Running class method in B...")# print(f"!!! {A.class_method()}")returnsuper().class_method()def__new__(cls):print("Running __new__...")returnsuper().__new__(cls)@staticmethoddefstatic_method(): # Super difficultprint("Running static method...")#return super(B, B).static_method()return A.static_method()# Working with class A (original, super-class)a =A()a.attr ="Object's own attr (from class A)"print(a.__dict__)print(a.instance_method())print(a.class_method())# print(A.static_method())print("--------"*10)# Working with class B (child to A and C)b =B()print(B.mro())b.attr ="Object's own attr (from class B)"print(b.instance_method())print(b.class_method())print(b.static_method())
More examples:
Base class abstraction
The dict that checks for int/str version of the key if it exists:
from collections import defaultdictclassVerboseDict(defaultdict): # new base classdef__setitem__(self,key,value):print(f'Set: {key} -> {value}')super().__setitem__(key, value)# no change neededvd =VerboseDict(int, x=1, y=2)vd["z"]=3vd[100]=100500vd[5]vd
Extending class, multi-inheritance
from collections import CounterclassVerboseDict(dict):def__setitem__(self,key,value):print(f'Set: {key} -> {value}')super().__setitem__(key, value)classVerboseCounter(VerboseDict,Counter):pass# This will use __setitem__ from Counter instead of dictprint(f'MRO for VerboseCounter is: {VerboseCounter.mro()}')counter =VerboseCounter("boombbbam")print(counter)
Extending list
This is an example useful to testing.
But in case you want to overload __new__ method please remember that it is static method so you need to pass cls:
from collections import UserListclassSuperList(list):def__new__(cls,data=None,*args): result =super().__new__(cls) result.append("START")if data: result.extend(reversed(data)) result.append("STOP")print("Before __init__:", result)return resultdef__init__(self,data=None,*args):'hiding list.__init__ which is resets the list'# if data:# self.extend(reversed(data))# self.append("STOP")passprint(SuperList())print(SuperList("abcde"))
There is also bigger example for this with lot of print() calls to see where exactly we are at each moment. Please use it to practice and to dig into this a bit more.
We can clearly see that firstly we call __new__ and after that __init__
classSuperList(list):def__new__(cls,*args,**kwargs):print(">>> Use parent's constructor but print this line!") original_list =super().__new__(cls, *args, **kwargs)print(">>> Original list:", original_list)print(id(original_list))return original_listdef__init__(self,*args,**kwargs):print(">>> Before running old __init__:", self)super().__init__(*args, **kwargs)print(">>> After running old __init__:", self) self.append("last element!")l =SuperList("abcde")print(type(l))print("New 'list':", l)l[0]=100del l[1]l[3:5]= ["AAA","BBB"]print("After changes:", l)print(id(l))
super() based on another class
As resume - we see that super() uses the MRO of passed class (by default it is the class we are defining method for). That's why if we pass another class directly (like it was in Python 2) it will use MRO of that class:
classA: defm(self):return"A"classB(A):defm(self):return"B"classC(B):defm(self):return"C"classD(C):defm(self):print(f"default super()'s MRO is {self.__class__.mro()}")print(f"super().m() -> {super().m()} (we take <m> from <C>)")print("~"*60)print(f"B's MRO is {B.mro()}")print(f"super(B, self).m() -> {super(B, self).m()} (we take <m> from <A>)")D().m()