I have the following two superclasses:
class Parent1(object):
def on_start(self):
print('do something')
class Parent2(object):
def on_start(self):
print('do something else')
I would like to have a child class that inherits from both be able to call super for both parents.
class Child(Parent1, Parent2):
def on_start(self):
# super call on both parents
What is the Pythonic way to do this? Thanks.
Exec summary:
Super only executes one method based on the class hierarchy's __mro__. If you want to execute more than one method by the same name, your parent classes need to written to cooperatively do that (by calling super implicitly or explicitly) or you need to loop over __bases__ or the __mro__ values of the child classes.
The job of super is to delegate part or all of a method call to some existing method in the classes ancestor tree. The delegation may go well outside of classes that you control. The method name delegated needs to exist in the group of base classes.
The method presented below using __bases__ with try/except is closest to a complete answer to your question of how to call each parent's method of the same name.
super is useful in the situation where you want to call one of your parent's methods, but you don't know which parent:
class Parent1(object):
pass
class Parent2(object):
# if Parent 2 had on_start - it would be called instead
# because Parent 2 is left of Parent 3 in definition of Child class
pass
class Parent3(object):
def on_start(self):
print('the ONLY class that has on_start')
class Child(Parent1, Parent2, Parent3):
def on_start(self):
super(Child, self).on_start()
In this case, Child has three immediate parents. Only one, Parent3, has an on_start method. Calling super resolves that only Parent3 has on_start and that is the method that is called.
If Child inherits from more than one class that has an on_start method, the order is resolved left to right (as listed in the class definition) and bottom to top (as logical inheritance). Only one of the methods is called and the other methods of the same name in the hierarchy of classes have been superseded.
So, more commonly:
class GreatGrandParent(object):
pass
class GrandParent(GreatGrandParent):
def on_start(self):
print('the ONLY class that has on_start')
class Parent(GrandParent):
# if Parent had on_start, it would be used instead
pass
class Child(Parent):
def on_start(self):
super(Child, self).on_start()
If you want to call multiple parents methods by method name, you can use __bases__ instead of super in this case and iterate over the base classes of Child without knowing the classes by name:
class Parent1(object):
def on_start(self):
print('do something')
class Parent2(object):
def on_start(self):
print('do something else')
class Child(Parent1, Parent2):
def on_start(self):
for base in Child.__bases__:
base.on_start(self)
>>> Child().on_start()
do something
do something else
If there is a possibility one of the base classes does not have on_start you can use try/except:
class Parent1(object):
def on_start(self):
print('do something')
class Parent2(object):
def on_start(self):
print('do something else')
class Parent3(object):
pass
class Child(Parent1, Parent2, Parent3):
def on_start(self):
for base in Child.__bases__:
try:
base.on_start(self)
except AttributeError:
# handle that one of those does not have that method
print('"{}" does not have an "on_start"'.format(base.__name__))
>>> Child().on_start()
do something
do something else
"Parent3" does not have an "on_start"
Using __bases__ will act similar to super but for each class hierarchy defined in the Child definition. ie, it will go though each forbearer class until on_start is satisfied once for each parent of the class:
class GGP1(object):
def on_start(self):
print('GGP1 do something')
class GP1(GGP1):
def on_start(self):
print('GP1 do something else')
class Parent1(GP1):
pass
class GGP2(object):
def on_start(self):
print('GGP2 do something')
class GP2(GGP2):
pass
class Parent2(GP2):
pass
class Child(Parent1, Parent2):
def on_start(self):
for base in Child.__bases__:
try:
base.on_start(self)
except AttributeError:
# handle that one of those does not have that method
print('"{}" does not have an "on_start"'.format(base.__name__))
>>> Child().on_start()
GP1 do something else
GGP2 do something
# Note that 'GGP1 do something' is NOT printed since on_start was satisfied by
# a descendant class L to R, bottom to top
Now imagine a more complex inheritance structure:
If you want each and every forbearer's on_start method, you could use __mro__ and filter out the classes that do not have on_start as part of their __dict__ for that class. Otherwise, you will potentially get a forbearer's on_start method. In other words, hassattr(c, 'on_start') is True for every class that Child is a descendant from (except object in this case) since Ghengis has an on_start attribute and all classes are descendant classes from Ghengis.
** Warning -- Demo Only **
class Ghengis(object):
def on_start(self):
print('Khan -- father to all')
class GGP1(Ghengis):
def on_start(self):
print('GGP1 do something')
class GP1(GGP1):
pass
class Parent1(GP1):
pass
class GGP2(Ghengis):
pass
class GP2(GGP2):
pass
class Parent2(GP2):
def on_start(self):
print('Parent2 do something')
class Child(Parent1, Parent2):
def on_start(self):
for c in Child.__mro__[1:]:
if 'on_start' in c.__dict__.keys():
c.on_start(self)
>>> Child().on_start()
GGP1 do something
Parent2 do something
Khan -- father to all
But this also has a problem -- if Child is further subclassed, then the child of Child will also loop over the same __mro__ chain.
As stated by Raymond Hettinger:
super() is in the business of delegating method calls to some class in
the instance’s ancestor tree. For reorderable method calls to work,
the classes need to be designed cooperatively. This presents three
easily solved practical issues:
1) the method being called by super() needs to exist
2) the caller and callee need to have a matching argument signature and
3) every occurrence of the method needs to use super()
The solution is to write cooperative classes that uniformly use super through the ancestor list or creative use of the adapter pattern to adapt classes you cannot control. These methods are discussed more completely in the article Python’s super() considered super! by Raymond Hettinger.
class Parent1(object):
def on_start(self):
print('do something')
class Parent2(object):
def on_start(self):
print('do something else')
class Child(Parent1, Parent2):
def on_start(self):
super(Child, self).on_start()
super(Parent1, self).on_start()
c = Child()
c.on_start()
do something
do something else
Or without super:
class Child(Parent1, Parent2):
def on_start(self):
Parent1.on_start(self)
Parent2.on_start(self)
In your case, since both of the parents implements the same method, super will just be the same as the first parent inherited, from left to right (for your code, Parent1). Calling two functions with super is impossible. To do what you want, you must simply call the method from the parent class, as follows:
class Child(Parent1, Parent2):
def on_start (self):
Parent1.on_start()
Parent2.on_start()
super() calls are meant to be cooperative.
One standard idiom for this goes as follows:
Everything ultimately inherits from a base that can discard unnecessary keyword arguments (to avoid an exception from forwarding them to object.__init__).
Every class calls super(), including the base.
Every __init__ accepts **kwargs; initializes local stuff according to specific keywords; then passes the information along the super chain.
The calling code will pass keyword args necessary for initializing every class in the chain.
So, for example:
class Person:
def __init__(self, **kwargs):
print(f'Initializing a Person')
pass
class Parent1(Person):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.heirloom1 = kwargs.get('heirloom1', None)
print(f'Set up Parent1 of the {self.__class__} with {self.heirloom1!r}')
class Parent2(Person):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.heirloom2 = kwargs.get('heirloom2', None)
print(f'Set up Parent2 of the {self.__class__} with {self.heirloom2!r}')
class Child(Parent1, Parent2):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.inheritance = kwargs.get('inheritance', None)
spoiled_rotten = Child(inheritance='mansion', heirloom1='trust fund', heirloom2='sports car')
Note in particular that, when spoiled_rotten is created, super().__init__(**kwargs) will forward to Parent2, not Person, and that Person.__init__ is only called once. super doesn't mean "the base class"; it means "the next class in the MRO of self" - and self is a Child instance throughout the entire process.
from abc import abstractmethod
class Parent:
""" Class from some library"""
def __init__(self):
# how can I tell the IDE that the child class should not overwrite this?
self.data = "don't touch this, some other method needs it"
#abstractmethod
def method(self):
pass
def some_other_method(self):
print(self.data)
class Child(Parent):
""" My specified class, that implements the abstract method"""
def __init__(self, data):
super().__init__()
# I did not look into all parents of all parents,
# How to know that this is dangerous?
self.data = data
def method(self):
print(self.data)
child = Child('hello')
child.method()
child.some_other_method()
Is there a way to mark the data attribute in the Parent class so that pylint (or a modern IDE) would warn about overwriting the attribute in the Child?
Or do I always have to check all attributes of all parents manually when using attributes in a child?
The only idea I have is checking for each attribute if it exists after initializing the parent before setting it in a child, but that also seems like some weird overhead to me.
assert not hasattr(super(), "data")
edit: To better explain what I mean, I am probably looking for something similar like the abstractmethod decorator for methods but for protected attributes instead.
For example, if I write something like this in my IDE:
class Parent:
""" Class from some library"""
#abstractmethod
def method(self):
pass
class Child(Parent):
""" My specified class, that should implement the abstract method"""
def __init__(self):
super().__init__()
My IDE complains about the missing implementation of method in the Child:
Class Child must implement all abstract methods
It would be cool to have something similar to mark attributes as protected like:
class Parent:
""" Class from some library"""
def __init__(self):
#protected # does not exist I am afraid
self.data = 'do not touch this'
class Child(Parent):
""" My specified class, that touches the data"""
def __init__(self):
super().__init__()
self.data = 'I touch it!' # should be marked with warning
For the linter/IDE to mark the overwritten protected attribute
Not really, but what you can do is to prefix your attribute with __ and will automatically will be rename when you're outside of the class. See code bellow
>>> class P :
def __init__():
self.__data = "no overwritable"
>>> class C(P):
def __init__(self):
super().__init__()
self.__data = "overwrited"
>>> c = C()
>>> c.__dict__
{'_P__data': 'no overwritable', '_C__data': 'overwrited'}
here your instance of C will have to data attribute, the one from the parent class P accessible through _P__data and the one from the child class C accessible through _C__data
In this way you don't always have to check if an attribute exists in a parent, but you can still access it if you really need it
code:
class Parent:
#classmethod
def method(cls):
...
class Child(Parent):
def method(cls):
...?
what do you put in Parent to make sure method in Child is decorated with classmethod without explicitly adding #classmethod in Child?
Is it possible to skip classes in the method resolution order when calling for methods?
For example,
super().super()
I read the docs here: https://docs.python.org/3/library/functions.html#super
that lead me to this code
class A:
def show(self):
print("A")
class B(A):
def __init__(self):
print("B")
def s(self):
return super()
class C(B):
def __init__(self):
super().s().show()
c = C()
c
See that super returns a proxy object that seems not to have the super method (because i tried and the interpreter told me it doesn't). But you do have the others methods from the class, so this way I could get a proxy from its grandparent to use its methods
super() in the class definition for FooClass is shorthand for super(FooClass, self). Using this, we can do this:
class Grandparent:
def test(self):
print('grandparent gets called')
class Parent(Grandparent):
def test(self):
super().test()
print('parent gets skipped')
class Child(Parent):
def test(self):
super(Parent, self).test()
print('child gets called')
This "cheats" the MRO by checking the parent's superclass instead of the child's superclass.
If I am inheriting from a class and not changing anything in a method, is it required to use super to initialize the method from the parent class?
class A:
def __init__(self):
self.html = requests.get("example.com").text
class B(A):
def __init__(self):
# is this needed?
super(B, self).__init__()
def new_method(self):
print self.html
Because you created a __init__ method in your class B, it overrides the method in class A. If you want it executed, you'll have to use super(), yes.
However, if you are not doing anything else in B.__init__, you may as well just omit it:
class A:
def __init__(self):
self.html = requests.get("example.com").text
class B(A):
def new_method(self):
print self.html
If you want to do anything in addition to what A.__init__() does, then it makes sense to create a B.__init__() method, and from that method, invoke the parent __init__.
It's not needed to define the overriding method at all. Python's default behavior is to call the method on the parent class (the next class in the method resolution order) if the current class doesn't override it.
>>> class Foo(object):
... def __init__(self):
... print("Foo")
...
>>> class Bar(Foo): pass
...
>>> Bar()
Foo
<__main__.Bar object at 0x7f5ac7d1b990>
Notice "Foo" got printed when I initialized a Bar instance.
If you do define the method, you need to call the super class's method (either explicitly or via super) if you want to make sure that it gets called.