I'm practicing OOP and keep running into this issue. Here's one example.
Take a diamond-shaped multiple class inheritance arrangement, with Weapon feeding Edge and Long, both of which are inherited by Zweihander. If I code Edge without inheriting Weapon, the code works fine. But as soon as I make Weapon its parent, Edge can't seem to find the argument for its 'sharpness' parameter anymore. It gives me a
TypeError: Edge.__init__() missing 1 required positional argument: 'sharpness'
Oddly, the final line referenced by the error is the super().init() line in the Long class constructor. If the eventual object I create is a Zweihander, it has Edge and gets all the Weapon elements via Long, so that's functionally acceptable. But if I want for instance a knife object that's just Edge, it needs to inherit Weapon, which breaks the program.
What am I missing? My best guess is an MRO issue, but I can't figure it out. Thanks, all.
class Weapon:
def __init__(self):
self.does_damage = "very yes"
def attack(self):
print("Je touche!")
class Edge(Weapon):
def __init__(self, sharpness):
super().__init__()
self.sharpness = sharpness
class Long(Weapon):
def __init__(self, length):
super().__init__()
self.length = length
class Zweihander(Long, Edge):
def __init__(self, name, length, sharpness):
Long.__init__(self, length)
Edge.__init__(self, sharpness)
self.name = name
def warning(self):
print("I will show you...\nTHE GREATEST NIGHTMARE!!!")
soulcal = Zweihander(name="soulcal", sharpness=100, length=54)
soulcal.warning()
CodePudding user response:
You only need to make a few small changes to each class's __init__
method to properly support cooperative multiple inheritance, per https://rhettinger.wordpress.com/2011/05/26/super-considered-super/
class Weapon:
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.does_damage = "very yes"
def attack(self):
print("Je touche!")
class Edge(Weapon):
def __init__(self, *, sharpness, **kwargs):
super().__init__(**kwargs)
self.sharpness = sharpness
class Long(Weapon):
def __init__(self, *, length, **kwargs):
super().__init__(**kwargs)
self.length = length
class Zweihander(Long, Edge):
def __init__(self, *, name, **kwargs):
super().__init__(**kwargs)
self.name = name
def warning(self):
print("I will show you...\nTHE GREATEST NIGHTMARE!!!")
soulcal = Zweihander(name="soulcal", sharpness=100, length=54)
soulcal.warning()
This approach follows a few simple rules:
- Every
__init__
method accepts arbitrary keyword arguments via**kwargs
. - Every
__init__
method callssuper.__init__(**kwargs)
. - Every
__init__
method defines keyword-only parameters instead of ordinary parameters, ensuring that keyword arguments are used at instantiation to simplify the delegation of each argument to the appropriate class.
For example, Zweihander.__init__
has its keyword-only argument name
set by a keyword argument, with the remaining keyword arguments collected in kwargs
. It neither knows nor cares what those arguments are; it just assumes that super().__init__
, whichever method that winds up being, will handle them appropriately.
Eventually, all keyword arguments will be consumed by one of the classes in the instance's MRO, or one or more will remain in kwargs
when object.__init__
is finally called, raising a TypeError
.