2

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 Hybrid, which inherits from more than one class, I get the following error:

Traceback (most recent call last):
  File "multi_inherit.py", line 44, in <module>
    hawk = Hybrid('hawk', 200, 650)
  File "multi_inherit.py", line 35, in __init__
    Archer.__init__(self, name, arrows)
  File "multi_inherit.py", line 13, in __init__
    super().__init__(name)
TypeError: Wizard.__init__() missing 1 required positional argument: 'power'

The other classes work absolutely fine.

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())
6
  • 2
    You are trying to use super wiht multiple inheritance, then every class should be using super. Your classes have to be designed to support this from the ground up. see: rhettinger.wordpress.com/2011/05/26/super-considered-super Commented Apr 27, 2022 at 20:35
  • 1
    Multiple inheritance, use of super(), parameter lists that are different for each class - pick any two, all three simply do not get along. The specific problem is that super() doesn't necessarily invoke the class you expect when there's a diamond in the inheritance graph - so you don't know what set of parameters to pass to it. Explicitly naming the parent class in each inherited call may be the simplest fix here. Commented Apr 27, 2022 at 20:37
  • 1
    What's basically happening is that you call Archer.__init__(self, name, arrows) where self is a Hybrid object, then in Archer.__init__, you call super().__init__(name). So the MRO of Hybrid is [__main__.Hybrid, __main__.Archer, __main__.Wizard, __main__.User, object], so super knows it has a hybrid object, in the Archer class, so it proxies to the next class in the Hybrid MRO, i.e. Wizard.__init__, but you only passed a single argument, and the second positional argument is missing Commented Apr 27, 2022 at 20:38
  • 1
    Also, check out the great PyCon talk: youtube.com/watch?v=EiOglTERPEo which goes into a lot of details and builds up examples of how to use cooperative inheritance. Commented Apr 27, 2022 at 20:40
  • In the Archer and Wizard classes if you replace super().__init__(name) with User.__init__(self, name) it will fix your problem. I don't think this is the proper way of fixing it though; someone else will probably come along with a better answer. Commented Apr 27, 2022 at 20:54

1 Answer 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.
Sign up to request clarification or add additional context in comments.

5 Comments

Yep, the mixin pattern is usually much, much clearner.
I like this approach - Thank you! Now I've just got to get to grips with it so I can use this comfortably at will.
Been practising this and it's really sinking in - makes a lot of sense. One question I have though is when stating the expected data type within the mixin classes, is there a reason why you use ':' as apposed to '=' For example when writing the UserMixin class you put 'name: str'
@ScottMButler : is used to apply a typehint annotation to the attribute name. It doesn't assign it to anything, which is what = is for. All it does is declare that there will be a attribute with that name/type available on concrete instances of the class.
@flakes Thanks so much for your help - greatly appreciated!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.