I have been practicing multiple inheritance, the example can sound weird but this is what I have in mind...
Here is a main class called Pet and that has two children Cat and Fish and, I wanted to mix the skills of the cat and the skills of the fish in another class but it seems not to work, some idea?
class Pet:
def __init__(self, name, age):
self.name = name
self.age = age
class Cat(Pet):
def __init__(self, name, age,climb):
super().__init__(name, age)
self.mtsClimbed = climb
class Fish(Pet):
def __init__(self, name, age, breathing, living):
super().__init__(name, age)
self.breathing = breathing
self.place_living = living
class fishCat(Cat, Fish):
def __init__(self, name, age, climb, breathing, living ):
Cat.__init__(self, name, age, climb)
Fish.__init__(self, name, age, breathing, living)
fishy = fishCat('Garfield', 2, '20 mts' , 'Water', 'Sea')
The error I got
line 15, in __init__super().__init__(name, age)
TypeError: Fish.__init__() missing 2 required positional arguments: 'breathing' and 'living'
CodePudding user response:
super()
doesn't call the first parent class' method. It calls the next method in the MRO, or method resolution order (link is for Python 2.3, but the same algorithm is still used today).
class Pet:
pass
class Cat(Pet):
pass
class Fish(Pet):
pass
class fishCat(Cat, Fish):
pass
With this inheritance hierarchy, the MRO of fishCat
is [fishCat, Cat, Fish, Pet, object]
. You can verify this in Python with the mro()
method on a class.
>>> fishCat.mro()
[<class '__main__.fishCat'>, <class '__main__.Cat'>, <class '__main__.Fish'>, <class '__main__.Pet'>, <class 'object'>]
That means that, if you have a fishCat
object and Cat.__init__
calls super().__init__
, then that will actually call Fish.__init__
, despite the fact that Cat
is not a subclass of Fish
.
Generally, if you're expecting to be dealing with complex diamond inheritance situations like this in Python, you should take all arguments as keyword arguments and have your constructor accept (and forward) any keyword arguments it doesn't recognize.
class Pet:
def __init__(self, name, age, **kwargs):
self.name = name
self.age = age
class Cat(Pet):
def __init__(self, climb, **kwargs):
super().__init__(**kwargs)
self.mtsClimbed = climb
class Fish(Pet):
def __init__(self, breathing, living, **kwargs):
super().__init__(**kwargs)
self.breathing = breathing
self.place_living = living
class fishCat(Cat, Fish):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Any fishCat-specific initialization goes here ...
This does force you to call constructors with keyword arguments only, so fishCat('Garfield', 2, '20 mts', 'Water', 'Sea')
must be replaced with fishCat(name='Garfield', age=2, climb='20 mts', breathing='Water', living='Sea')
. But given the complex inheritance hierarchy you have, this level of verbosity is probably an improvement.
Side note: It's worth questioning whether the diamond inheritance is really the best solution here. If it's possible for a thing to be a cat and a fish in your domain model, you might consider some other technique than subclassing. For instance, the "pet types" could be an attribute on your class, and an instance of Pet
(which would no longer have subclasses in this example) would have a list self.pet_attributes
which indicates all of the things it's capable of. From the small sample you've shown here, it seems to me that subclassing may not be the most ideal solution here.