Home > other >  Multiple class inheritance TypeError with one grandparent, two parents, one child class
Multiple class inheritance TypeError with one grandparent, two parents, one child class

Time:11-30

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:

  1. Every __init__ method accepts arbitrary keyword arguments via **kwargs.
  2. Every __init__ method calls super.__init__(**kwargs).
  3. 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.

  • Related