'Multiple Inheritance in Python - TypeError

really struggling with this. It's been bugging for the past couple of days and I cannot get my head around it, nor find an answer that fully makes sense to me.

I'm only really getting to grips with OOP and I have been looking at multiple inheritance. The problem I have, is when I instantiate the class which inherits from more than one class, I get the following error:

TypeError: Wizard.__init__() missing 1 required positional argument: 'power'

The other classes work absolutely fine, it's just when I instantiate the 'Hybrid' class, I get the error.

Here is the code:

class User:
    def __init__(self, name):
        self.name = name

    def greet(self):
        return f"Welcome, {self.name.title()}."


class Archer(User):
    def __init__(self, name, arrows):
        super().__init__(name)
        self.arrows = arrows

    def shoot_arrow(self):
        self.arrows -= 1
        return "You shot an arrow!"

    def arrows_left(self):
        return f"You have {self.arrows} arrows remaining."


class Wizard(User):
    def __init__(self, name, power):
        super().__init__(name)
        self.power = power

    def cast_spell(self):
        return f"You cast a spell with a power of {self.power}"


class Hybrid(Archer, Wizard):
    def __init__(self, name, arrows, power):
        Archer.__init__(self, name, arrows)
        Wizard.__init__(self, name, power)

    def powerful(self):
        return "Hybrids are super powerful!"


merlin = Wizard('merlin', 1000)
robin = Archer('robin', 150)
hawk = Hybrid('hawk', 200, 650)


print(merlin.greet())
print(merlin.cast_spell())

print(robin.arrows_left())
print(robin.shoot_arrow())
print(robin.arrows_left())

Any help would be amazing and any explanations understandable for someone new to this field would be greatly appreciated.



Solution 1:[1]

The classes as you have them now aren't great for use in multiple inheritance. When using multiple inheritance I prefer having constructors that agree on the same contract (use the same arguments) or no arguments at all. Here powers and arrows differ which makes calling each constructor awkward.

IMO A better way to design this class would be mixins. The mixins would have no constructors and depend on particular values being present in the classes which extend them.

Example Mixins:

class UserMixin:
    name: str

    def greet(self):
        return f"Welcome, {self.name.title()}."


class ArcherMixin(UserMixin):
    arrows: int

    def shoot_arrow(self):
        self.arrows -= 1
        return "You shot an arrow!"

    def arrows_left(self):
        return f"You have {self.arrows} arrows remaining."


class WizardMixin(UserMixin):
    power: int

    def cast_spell(self):
        return f"You cast a spell with a power of {self.power}"

Example Implementations:

class User(UserMixin):
    def __init__(self, name):
        self.name = name


class Archer(ArcherMixin):
    def __init__(self, name, arrows):
        self.name = name
        self.arrows = arrows


class Wizard(WizardMixin):
    def __init__(self, name, power):
        self.name = name
        self.power = power


class Hybrid(ArcherMixin, WizardMixin):
    def __init__(self, name, arrows, power):
        self.name = name
        self.arrows = arrows
        self.power = power

    def powerful(self):
        return "Hybrids are super powerful!"

Example usage:

merlin = Wizard('merlin', 1000)
robin = Archer('robin', 150)
hawk = Hybrid('hawk', 200, 650)

print(merlin.greet())
print(merlin.cast_spell())

print(robin.greet())
print(robin.arrows_left())
print(robin.shoot_arrow())
print(robin.arrows_left())

print(hawk.greet())
print(hawk.cast_spell())
print(hawk.arrows_left())
print(hawk.shoot_arrow())
print(hawk.arrows_left())
Welcome, Merlin.
You cast a spell with a power of 1000
Welcome, Robin.
You have 150 arrows remaining.
You shot an arrow!
You have 149 arrows remaining.
Welcome, Hawk.
You cast a spell with a power of 650
You have 200 arrows remaining.
You shot an arrow!
You have 199 arrows remaining.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 flakes