When working with multiple inheritance, I'm trying to understand why it works if you refer to the parents' class names in the child class, but it does not work if you use super(). Any help is appreciated.
#DOES NOT WORK WITH super()
#Parent 1 class
class Husband:
def __init__(self,strength):
self.strength = strength
#Parent 2 class
class Wife:
def __init__(self,looks):
self.looks = looks
#Child class
class Son(Husband,Wife):
def __init__(self,strength,looks):
super().__init__(self,strength)
super().__init__(self,looks)
son = Son('Goliath','GQ')
print(son.strength,son.looks)
#WORKS WHEN CALLING PARENT CLASS NAMES
#Parent 1 class
class Husband:
def __init__(self,strength):
self.strength = strength
#Parent 2 class
class Wife:
def __init__(self,looks):
self.looks = looks
#Child class
class Son(Husband,Wife):
def __init__(self,strength,looks):
Husband.__init__(self,strength)
Wife.__init__(self,looks)
son = Son('Goliath','GQ')
print(son.strength,son.looks)
Super doesn't mean "parent", it means "next in line in the MRO of this object".
So if you make a class that you want to be inherited from, you should probably be calling super() in that parent class as well.
If self is a Son object, then within the body of the Husband.__init__(self) method, super() delegates to Wife, not to general object. You should only need one super().__init__ call within Son, and then the super call in Husband.__init__ can delegate to Wife.__init__. (I'm not sure subclassing is the correct relationship for this case: I wouldn't say a Son is-a Wife). You may also need to update the __init__ signatures to accept arbitrary **kwargs so that everything can be passed along in a compatible way.
Here's a blog post that I think is helpful: https://rhettinger.wordpress.com/2011/05/26/super-considered-super/
There's a video on YouTube with the same title "Super Considered Super" that I think is helpful as well.
In short, if your class has multiple inheritance, a plain call to super() always refer to the Husband class.
You may want to look for a concept called MRO (Method resolution order).
Related
For classes:
class Base(ABC):
def __init__(self, param1):
self.param1 = param1
#abstractmethod
def some_method1(self):
pass
# #abstractmethod
# def potentially_shared_method(self):
# ????
class Child(Base):
def __init__(self, param2):
self.param1 = param1
self.param2 = param2
def some_method1(self):
self.object1 = some_lib.generate_object1(param1, param2)
def potentially_shared_method(self):
return object1.process()
I want to move the potentially_shared_method to be shared in abstract calss, however it uses object1 that is initialized in some_method1 and needs to stay there.
If it's only potentially shared, it doesn't belong in the base class. You'd be breaking a few design principles.
What is a child class supposed to do for which the sharing doesn't make sense?
Also, you're introducing some temporal coupling; you can only call potentially_shared_method after some_method1 has been called. That's not ideal because the users of your class might not realize that.
Also, if the method is shared, you probably don't want it to be abstract in your base class; with an abstract method you're really only sharing the signature; but it seems you'll want to share functionality.
Anyway. Here's some options:
Using Python's multiple inheritance, move potentially_shared_method into a SharedMixin class and have those children who share it inherit from Base and from SharedMixin. You can then also move some_method1 into that SharedMixin class because it seems to me that those go together. Or maybe not...
Hide the access to object1 behind a getter. Make the getter have a dummy implementation in the base class and a proper implementation in those child classes who actually create an object1. Then potentially_shared_method can be moved to Base and just refer to the getter.
I looked at answers https://stackoverflow.com/a/33469090/11638153 and https://stackoverflow.com/a/27134600 but did not understand what is "indirection." To be specific, if I have classes like below, are there any drawbacks or advantages in hardcoding the parent class __init__(), as done in class Child1 (why do we need super() when one can explicitly write __init__() methods to invoke)?
class Base1:
def __init__(self):
print("Class Base1 init()")
class Base2:
def __init__(self):
print("Class Base2 init()")
class Child1(Base1, Base2):
def __init__(self):
print("Class Child1 init()")
Base1.__init__(self)
Base2.__init__(self)
class Child2(Base1, Base2):
def __init__(self):
print("Class Child2 init()")
super().__init__()
if __name__ == "__main__":
obj1 = Child1()
print("---")
obj2 = Child2()
Roughly, the difference is that ParentClass.__init__(self) always calls the specifically named class while super().__init__() makes a runtime computation to determine which class to call.
In many situations, the two ways give the same results. However, super() is much more powerful. It a multiple inheritance scenario, it can potentially call some class other than the parent of the current class. This article covers the latter scenario is detail.
For single inheritance, the reason to prefer super() is that that it read nicely and that it makes maintenance easier by computing the parent class.
For multiple inheritance, super() is the only straightforward way to access parent classes in the order of the MRO for the class of the calling instance. If those words don't mean anything to you, please look at the referenced article.
In your example code, Child1's design is very fragile. If its two base classes share some common base (and call their own parents' __init__ method inside of their own turn), that grandparent class might get initialized more than once, which would be bad if it's computationally costly, or has side effects. This is the "diamond pattern" of inheritance hierarchy that can be troublesome in many languages (like C++) that allow multiple inheritance.
Here's an example of the bad problem that can come up with explicit parent calls, and which super() is designed to fix:
class Grandparent:
def __init__(self):
print("Grandparent")
class Parent1(Grandparent):
def __init__(self):
print("Parent1")
Grandparent.__init__(self)
class Parent2(Grandparent):
def __init__(self):
print("Parent2")
Grandparent.__init__(self)
class Child(Parent1, Parent2):
def __init__(self):
print("Child")
Parent1.__init__(self)
Parent2.__init__(self)
c = Child() # this causes "Grandparent" to be printed twice!
Python's solution is to use super() which allows collaborative multiple inheritance. Each class must be set up to use it, including the intermediate base classes! Once you set everything up to use super, they'll call each other as necessary, and nothing will ever get called more than once.
class Grandparent:
def __init__(self):
print("Grandparent")
class Parent1(Grandparent):
def __init__(self):
print("Parent1")
super().__init__()
class Parent2(Grandparent):
def __init__(self):
print("Parent2")
super().__init__()
class Child(Parent1, Parent2):
def __init__(self):
print("Child")
super().__init__()
c = Child() # no extra "Grandparent" printout this time
In fact, every instance of multiple-inheritance in Python is a diamond shape, with the Grandparent class of object (if not something else). The object class has an __init__ method that takes no arguments (other than self) and does nothing.
It's important to note that the super().__init__ calls from the ParentX classes don't always go directly to Grandparent. When initializing an instance of Parent1 or Parent2, they will, but when instantiating an instance of Child, then Parent1.__init__'s call will go to Parent2.__init__, and only from there will Parent2's super call go to Grandparent.__init__.
Where a super call resolves to depends on the MRO (Method Resolution Order) of the instance the methods are being called on. A super call generally means "find this method in the next class in the MRO" relative to where it's called from. You can see the MRO of a class by calling SomeClass.mro(). For Child in my examples, it's: [Child, Parent1, Parent2, Grandparent, object]
According to Python docs super()
is useful for accessing inherited methods that have been overridden in
a class.
I understand that super refers to the parent class and it lets you access parent methods. My question is why do people always use super inside the init method of the child class? I have seen it everywhere. For example:
class Person:
def __init__(self, name):
self.name = name
class Employee(Person):
def __init__(self, **kwargs):
super().__init__(name=kwargs['name']) # Here super is being used
def first_letter(self):
return self.name[0]
e = Employee(name="John")
print(e.first_letter())
I can accomplish the same without super and without even an init method:
class Person:
def __init__(self, name):
self.name = name
class Employee(Person):
def first_letter(self):
return self.name[0]
e = Employee(name="John")
print(e.first_letter())
Are there drawbacks with the latter code? It looks so much cleanr to me. I don't even have to use the boilerplate **kwargs and kwargs['argument'] syntax.
I am using Python 3.8.
Edit: Here's another stackoverflow questions which has code from different people who are using super in the child's init method. I don't understand why. My best guess is there's something new in Python 3.8.
The child might want to do something different or more likely additional to what the super class does - in this case the child must have an __init__.
Calling super’s init means that you don’t have to copy/paste (with all the implications for maintenance) that init in the child’s class, which otherwise would be needed if you wanted some additional code in the child init.
But note there are complications about using super’s init if you use multiple inheritance (e.g. which super gets called) and this needs care. Personally I avoid multiple inheritance and keep inheritance to aminimum anyway - it’s easy to get tempted into creating multiple levels of inheritance/class hierarchy but my experience is that a ‘keep it simple’ approach is usually much better.
The potential drawback to the latter code is that there is no __init__ method within the Employee class. Since there is none, the __init__ method of the parent class is called. However, as soon as an __init__ method is added to the Employee class (maybe there's some Employee-specific attribute that needs to be initialized, like an id_number) then the __init__ method of the parent class is overridden and not called (unless super.__init__() is called) and then an Employee will not have a name attribute.
The correct way to use super here is for both methods to use super. You cannot assume that Person is the last (or at least, next-to-last, before object) class in the MRO.
class Person:
def __init__(self, name, **kwargs):
super().__init__(**kwargs)
self.name = name
class Employee(Person):
# Optional, since Employee.__init__ does nothing
# except pass the exact same arguments "upstream"
def __init__(self, **kwargs):
super().__init__(**kwargs)
def first_letter(self):
return self.name[0]
Consider a class definition like
class Bar:
...
class Foo(Person, Bar):
...
The MRO for Foo looks like [Foo, Person, Bar, object]; the call to super().__init__ inside Person.__init__ would call Bar.__init__, not object.__init__, and Person has no way of knowing if values in **kwargs are meant for Bar, so it must pass them on.
The title is pretty much self explanatory, but I think this is better explained with an example.
class Dog():
def __init__(self, name):
self.name = name
def get_name(self):
return self.name
def get_color(self):
return body_color()
class personality_1():
def get_happiness(self):
return happiness_with_owner()
def get_sadness(self):
return sadness()
## A lot more personality methods here
class SocialDog(Dog):
# Override regular method
def get_color(self):
return face_color()
# I want to override the personality 1 class but not completely, just one method
class personality_2(>>>How to inherit from personality_1?<<<):
# Now, I would like to override just one method of personality 1:
def get_happiness(self):
return happiness_with_others()
Hopefully the logic is correct. I was trying to use super() with no success. Hopefully I can find a solution without using an explicit call to the parent class.
Any thoughts?
Thanks in advance!
To inherit from the class you specified, according to the code you provided, all that is required is to define the class personality_2 like this:
class SocialDog(Dog):
#...
class personality_2(Dog.personality_1):
#...
Now, as for your problem when trying to use super(), this might be because your base classes of Dog and Dog.personality_1 do not inherit from the python default class object which is required in order to use the super() method. See this answer for details. If that is what you are after, all you need to do is modify your class declarations for Dog and Dog.personality_1 (or whatever they ultimately derive from) to the following:
class Dog(object):
#...
class personality_1(object):
#...
Then you can treat SocialDog.personality_2 just like any other subclass. If you are using python 2, remember when using super() that you need to use the fully qualified name:
super(SocialDog.personality_2, self).super_class_method()
super(SocialDog.personality_2, self).super_class_field
Use the name of the outer class to reach the inner class:
class SocialDog(Dog):
class personality_2(Dog.personality_1):
# ...
That said, this is a very weird thing you're doing, hiding the personality classes inside the dog classes, then using them outside...
If a personality is that tightly coupled to a specific class like Dog or SocialDog, what makes personality_2 think it's safe to mess with the behaviour of personality_1? In fact, the personality methods should probably be Dog or SocialDog methods instead.
Or, if it doesn't really matter which dog gets which personality, why not leave the personality classes up at the module level, where they can be instantiated and inherited like any other class? Your various Dog-derived classes would then take an optional personality argument when created:
class WolfPersonality(DogPersonality):
# ...
class Wolf(Dog):
def __init__(self, personality=None):
if personality is None:
personality = WolfPersonality()
self.personality = personality
# ...
# Later...
wolf = Wolf()
wolf_puppy = Wolf(FriendlyDogPersonality())
Considering:
class Parent(object):
def altered(self):
print "PARENT altered()"
class Child(Parent):
def altered(self):
print "CHILD, BEFORE PARENT altered()"
super(Child, self).altered() # what are the arguments needed? Why Child and self?
print "CHILD, AFTER PARENT altered()"
In Python 2.7, Why must Child be passed as an argument to the super() call? What are the exact intricacies of using super instead of just letting it work.
super figures out which is the next class in the Method Resolution Order. The two arguments you pass in are what lets it figure that out - self gives it the entire MRO via an attribute; the current class tells it where you are along the MRO right now. So what super is actually doing is basically:
def super(cls, inst):
mro = inst.__class__.mro() # Always the most derived class
return mro[mro.index(cls) + 1]
The reason it is the current class rather than the base class is because the entire point of having super is to have a function that works out what that base class is rather than having to refer to it explicitly - which can cause problems if the base class' name changes, if you don't know exactly what the parent class is called (think of factory functions like namedtuple that spit out a new class), and especially in multi-inheritance situations (where the next class in the MRO mightn't be one of the current class' bases).
You don't have to pass the child instance as an argument from python3 onwards if I'm not mistaken. Plus for an in-depth understanding of the super() method refer to https://rhettinger.wordpress.com/2011/05/26/super-considered-super/