Reputation: 199
Here's the setup:
class Player(object):
def __init__(self, heigth):
self.heigth = heigth
print('do not forget that this should happen once!')
class Attacker(Player):
def __init__(self, heigth, goal_probability):
super().__init__(heigth)
self.goal_prob = goal_probability
def hit(self):
pass
# implementation
class Goalie(Player):
def __init__(self, heigth, save_probability=0.1):
super().__init__(heigth)
self.save_prob = save_probability
def catch(self):
pass
# implementation
class UniversalPlayer(Attacker, Goalie):
pass
up = UniversalPlayer(heigth=1.96, goal_probability=0.6)
It all works as expected: the MRO chooses Attacker
first, then Goalie
. I call UniversalPlayer
's constructor with Attacker
's __init__
signature, Goalie
's constructor is called with Player
's signature, it goes ok because save_probability
has a default value but the problem is that I have no way of choosing save_probability
, apart from setting up.save_probability
after instantiating up
, which I find very inelegant.
Furthermore, had Goalie
not had a default value for save_probability
, this code would raise an exception.
Is there a way to write UniversalPlayer
so that I can choose save_probability
too, or is there some fundamental problem here that cannot be worked around?
Upvotes: 1
Views: 253
Reputation: 1747
Besides the fact I'm not sure if separate classes is the best way to handle these, the issue is that your constructors can't handle unknown arguments. To allow them to use the *args, **kwargs
notation.
Effectively all arguments will be passed to each __init__
and the unused ones ignored.
class Player(object):
def __init__(self, *args, **kwargs):
self.height = kwargs['height']
class Attacker(Player):
def __init__(self, goal_probability, *args, **kwargs):
super().__init__(*args, **kwargs)
self.goal_prob = goal_probability
def hit(self):
pass
# implementation
class Goalie(Player):
def __init__(self, save_probability, *args, **kwargs):
super().__init__(*args, **kwargs)
self.save_prob = save_probability
def catch(self):
pass
# implementation
class UniversalPlayer(Attacker, Goalie):
pass
up = UniversalPlayer(height=1.96, goal_probability=0.6, save_probability=0.2)
Upvotes: 0
Reputation: 531345
Each additional parameter to __init__
needs to have a class responsible for removing it from calls to super
, so that when object.__init__
is finally called, you don't accidentally pass any arguments to it. Additionally, each method has to accept arbitrary arguments and pass them on for the next method to possibly handle.
# Player will be responsible for height
class Player(object):
def __init__(self, height, **kwargs):
super().__init__(**kwargs) # Player needs to use super too!
self.height = height
print('do not forget that this should happen once!')
# Attacker will be responsible for goal_probability
class Attacker(Player):
def __init__(self, height, goal_probability, **kwargs):
super().__init__(height, **kwargs)
self.goal_prob = goal_probability
def hit(self):
pass
# Goalie will be responsible for save_probability
class Goalie(Player):
def __init__(self, height, save_probability=0.1, **kwargs):
super().__init__(height, **kwargs)
self.save_prob = save_probability
def catch(self):
pass
# implementation
class UniversalPlayer(Attacker, Goalie):
pass
# Pass all arguments
# Life is easier if you stick to keyword arguments when using super().__init__
up = UniversalPlayer(height=1.96, goal_probability=0.6, save_probability=0.2)
Now, Attacker.__init__
is the first to be called. It uses goal_probability
, then does not pass it on to other calls. It accepts save_probability
via **kwargs
and passes it on for Goalie.__init__
to eventually receive. Note that neither Attacker.__init__
nor Goalie.__init__
would have to explicitly include height
in their argument lists; it could also be accepted via **kwargs
to be eventually received by Player.__init__
.
Upvotes: 3