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:
class A:
attr = "class attr from A"
def instance_method(self):
print("Running method of A")
return self.attr
@classmethod
def class_method(cls):
return cls.attr
@staticmethod
def static_method():
return "Some static data (from A class)"
class C:
def instance_method(self):
return "I AM FROM C"
class B(A, C):
attr = "class attr from B"
def instance_method(self):
print("Running instance method in B...")
#return A.instance_method(self)
return super().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)
@classmethod
def class_method(cls):
print("Running class method in B...")
# print(f"!!! {A.class_method()}")
return super().class_method()
def __new__(cls):
print("Running __new__...")
return super().__new__(cls)
@staticmethod
def static_method(): # Super difficult
print("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:
class FlexibleDict(dict):
def __getitem__(self, key):
try:
if key in self:
pass
elif str(key) in self:
key = str(key)
elif int(key) in self:
key = int(key)
except ValueError:
pass
return dict.__getitem__(self, key)
fd = FlexibleDict()
fd[1] = 100500
print(fd["1"])
from collections import defaultdict
class VerboseDict(defaultdict): # new base class
def __setitem__(self, key, value):
print(f'Set: {key} -> {value}')
super().__setitem__(key, value) # no change needed
vd = VerboseDict(int, x=1, y=2)
vd["z"] = 3
vd[100] = 100500
vd[5]
vd
Extending class, multi-inheritance
from collections import Counter
class VerboseDict(dict):
def __setitem__(self, key, value):
print(f'Set: {key} -> {value}')
super().__setitem__(key, value)
class VerboseCounter(VerboseDict, Counter):
pass
# This will use __setitem__ from Counter instead of dict
print(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 UserList
class SuperList(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 result
def __init__(self, data=None, *args):
'hiding list.__init__ which is resets the list'
# if data:
# self.extend(reversed(data))
# self.append("STOP")
pass
print(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__
class SuperList(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_list
def __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] = 100
del 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:
class A:
def m(self):
return "A"
class B(A):
def m(self):
return "B"
class C(B):
def m(self):
return "C"
class D(C):
def m(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()
More examples:
class A1:
attr = 1
class A2:
attr = 2
class A3:
attr = 3
class AResult(A1, A2, A3):
def __call__(self):
return super().attr
AResult()()
The same as super():
class AResult(A1, A2, A3):
def __call__(self):
return super(self.__class__, self).attr
AResult()()
To start FROM A2:
class AResult(A1, A2, A3):
def __call__(self):
return super(A1, self).attr
AResult()()
class AResult(A1, A2, A3):
def __call__(self):
return super(A2, self).attr
AResult()()
Simple super(cls, obj) implementation
class super:
def __init__(self, *args, **kwargs):
print(args)
self.start_cls, self.instance = args
def __getattr__(self, attr):
print(f"Running __getattr__, attr = {attr}")
start = False
for cls in self.instance.__class__.mro():
if not start and cls != self.start_cls:
continue
if cls == self.start_cls:
start = True
continue
if hasattr(cls, attr):
ret = getattr(cls, attr)
if callable(ret):
return lambda: ret(self.instance)
return ret
class A:
def m(self):
return "A"
class B:
def m(self):
return "B"
class C:
def m(self):
return "C"
class D(A, B, C):
def m(self):
return super(B, self).m()
print(D.mro())
D().m()