Reputation: 1386
I am practicing multiple inheritances in Python.
Without the Boss
class, everything goes well. Any help is highly appreciated.
I've referred to :How does Python's super() work with multiple inheritance?
The feedback:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
in
41 print(archer1.__str__())
42 print('')
---> 43 boss = Boss("Boss", 50, 50, 100)
44 print(boss.__str__())
in __init__(self, name, power, agility, HP)
27 class Boss(Worrior,Archer):
28 def __init__(self, name, power, agility, HP):
---> 29 Worrior.__init__(self, name, power, HP)
30 Archer.__init__(self, name, agility, HP)
31 def __str__(self):
in __init__(self, name, power, HP)
7 class Worrior(Player):
8 def __init__(self, name, power, HP):
----> 9 super().__init__(HP)
10 self.name = name
11 self.power = power
TypeError: __init__() missing 2 required positional arguments: 'agility' and 'HP'
It seems after taking the power attribute inside the Worrior
class, and then stop.
class Player:
def __init__(self,HP):
self.HP = HP
def sign_in(self):
print('player sign in')
# put the class want to extend from
class Worrior(Player):
def __init__(self, name, power, HP):
super().__init__(HP)
self.name = name
self.power = power
# it's the toString() method in java
# need to override the dunder(magic) method
def __str__(self):
return "The worrior's name: " f'{self.name} \n' \
"He has the power:" f'{self.power}'
class Archer(Player):
def __init__(self, name, agility, HP):
super().__init__(HP)
self.name = name
self.agility = agility
def __str__(self):
return "The archer's name: " f'{self.name} \n' \
"He has the agility:" f'{self.agility}'
class Boss(Worrior,Archer):
def __init__(self, name, power, agility, HP):
Worrior.__init__(self, name, power, HP)
Archer.__init__(self, name, agility, HP)
def __str__(self):
return "The boss's name: " f'{self.name} \n' \
"With Worrior's power " f'{self.power} \n' \
"and With Archer's agilit" f'{self.agility}'\
"The boss' HP is: " f'{self.HP}'
boss = Boss("Boss", 50, 50, 100)
print(boss.__str__())
Upvotes: 2
Views: 2956
Reputation: 574
This is happening because of the method resolution order (MRO)
in new style classes.
MRO of the Boss class -
ipdb> Boss.mro()
[<class '__main__.Boss'>, <class '__main__.Worrior'>, <class '__main__.Archer'>, <class '__main__.Player'>, <class 'object'>]
ipdb>
This means that super() calls in Worrior class is actually referring to Archer, not Player.
You can refer to this stack overflow post - Method Resolution Order (MRO) in new-style classes?, which explains nicely how MRO works in python.
Upvotes: 1
Reputation: 3562
The link from @Thierry Lathuille is the correct one to read, but I'll try to add some extra explanation. The MRO for the initializer is [Boss, Worrior, Archer, Player]. What this means (somewhat confusingly) is that when Worrior calls super(), this is actually referring to Archer, not Player. If you place print(super().__init__)
before each time you call the method, you will find output like this prior to your crash:
<bound method Worrior.__init__ of <__main__.Boss object at 0x10af5f780>>
<bound method Archer.__init__ of <__main__.Boss object at 0x10af5f780>>
Traceback (most recent call last):
...
This is the major pitfall IMHO with multiple inheritance in Python, and I generally advise against it unless you have zero-argument initializers.
If you try to explicitly call each base class initializer (e.g. Player.__init__
), then you will end up with some of your initializers executing multiple times.
In order to be able to pass your arguments through, you'll need to leverage **kwargs. Additionally, as these get passed through, each unique argument name will get stripped from the kwargs. So this means that you can't reuse name
as an initialization argument. I'll use worrior_name
and archer_name
instead. So putting this all together, the following will run each initializer only once, without crashing:
class Player:
def __init__(self, hp, **kwargs):
print(super().__init__)
self.hp = hp
def sign_in(self):
print('player sign in')
class Worrior(Player):
def __init__(self, worrior_name, power, **kwargs):
super().__init__(**kwargs)
self.name = worrior_name
self.power = power
def __str__(self):
return "The worrior's name: " f'{self.name} \n' \
"He has the power:" f'{self.power}'
class Archer(Player):
def __init__(self, archer_name, agility, **kwargs):
super().__init__(**kwargs)
self.name = archer_name
self.agility = agility
def __str__(self):
return "The archer's name: " f'{self.name} \n' \
"He has the agility:" f'{self.agility}'
class Boss(Worrior, Archer):
def __init__(self, name, power, agility, hp):
super().__init__(archer_name=name, worrior_name=name, power=power, agility=agility, hp=hp)
def __str__(self):
return "The boss's name: " f'{self.name} \n' \
"With Worrior's power " f'{self.power} \n' \
"and With Archer's agilit" f'{self.agility}' \
"The boss' hp is: " f'{self.hp}'
Upvotes: 3
Reputation: 31461
It appears that Python decides to walk the entire hierarchy again when it arrives at super()
in the Warrior class (I've corrected your spelling, it's not 'Worrior').
I'm not sure where you're going wrong or whether this is just a situation where you cannot use super()
. I expected the same as you and was surprised by the result as well.
However, the code below doesn't have the problem, although you of course lose the flexibility and I don't like having to do this:
class Player:
def __init__(self, hp):
self.hp = hp
def sign_in(self):
print('player sign in')
class Warrior(Player):
def __init__(self, name, power, hp):
Player.__init__(self, hp)
self.name = name
self.power = power
def __str__(self):
return "The warrior's name: " f'{self.name} \n' \
"He has the power:" f'{self.power}'
class Archer(Player):
def __init__(self, name, agility, hp):
Player.__init__(self, hp)
self.name = name
self.agility = agility
def __str__(self):
return "The archer's name: " f'{self.name} \n' \
"He has the agility:" f'{self.agility}'
class Boss(Warrior, Archer):
def __init__(self, name, power, agility, hp):
Warrior.__init__(self, name, power, hp)
Archer.__init__(self, name, agility, hp)
def __str__(self):
return "The boss's name: " f'{self.name} \n' \
"With Warrior's power: " f'{self.power} \n' \
"and With Archer's agility: " f'{self.agility}'\
"The boss' HP is: " f'{self.hp}'
boss = Boss("Boss", 50, 50, 100)
print(boss.__str__())
So, not really a complete answer, but wanted to provide the feedback - hope someone else provides a complete answer with an explanation of what is really going on.
Upvotes: 0