Reputation: 371
I have to model a warrior and the different kinds of attacks he can perform. The idea is to use mixins to contain the attack logic. I have my classes defined in the following way:
class Warrior:
def __init__(self, energy):
self.energy = energy
class TemplarKnight(Warrior, HandToHandCombatMixin):
pass
class CombatMixin:
def __init__(self):
self.attacks_cost = {}
def attack(self, attacker, attack_cost):
if attacker.energy < attack_cost:
print('Not enough energy to attack')
else:
attacker.energy -= attack_cost
print('Attack!')
class HandToHandCombatMixin(CombatMixin):
def __init__(self):
super().__init__()
self.attacks_cost['sword_spin'] = 10
def sword_spin(self, attacker):
return self.attack(attacker, self.attacks_cost['sword_spin'])
But the problem comes when I try to test this setup. When I do
class TestTemplarKnight(unittest.TestCase):
def setUp(self):
self.templar = TemplarKnight(energy=100)
def test_templar_knight_can_sword_spin(self):
self.templar.sword_spin(self.warrior)
self.assertEquals(self.templar.energy, 90)
I get
def sword_spin(self, attacker):
return self.attack(
> attacker, self.attacks_cost['sword_spin'])
E AttributeError: 'TemplarKnight' object has no attribute 'attacks_cost'
It seems that Python thinks that the parameter self.attacks_cost
(when calling self.attack()
inside the sword_spin()
method of the HandToHandCombatMixin
class) belongs to the TemplarKnight
class instead of the HandToHandCombatMixin
.
How should I have written this code to make Python look for self.attacks_cost
inside HandToHandCombatMixin
?
Upvotes: 1
Views: 1628
Reputation: 531490
To use super
correctly, all the classes involved need to use it. Right now, Warrior.__init__
is called first, but it doesn't use super
, so HandToHandCombatMixin.__init__
is never called.
Make the following additions:
class Warrior:
def __init__(self, energy, **kwargs):
super().__init__(**kwargs)
self.energy = energy
class TemplarKnight(Warrior, HandToHandCombatMixin):
pass
class CombatMixin:
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.attacks_cost = {}
def attack(self, attacker, attack_cost):
if attacker.energy < attack_cost:
print('Not enough energy to attack')
else:
attacker.energy -= attack_cost
print('Attack!')
class HandToHandCombatMixin(CombatMixin):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.attacks_cost['sword_spin'] = 10
def sword_spin(self, attacker):
return self.attack(attacker, self.attacks_cost['sword_spin'])
Now when you instantiate TemplarKnight
, you'll guarantee that all the __init__
methods are called, and in the correct order. Eventually, once of the calls to super()
will cause object.__init__
to be called, at which point the chain finally ends. If you are correctly handling the keyword arguments, **kwargs
will be empty by the time that happens.
Upvotes: 3