2

I am practicing multiple inheritances in Python. Without the Boss class, everything goes well. Any help is highly appreciated. I've referred to :How does Python's super() work with multiple inheritance?

The feedback:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
 in 
 41 print(archer1.__str__())
 42 print('')
---> 43 boss = Boss("Boss", 50, 50, 100)
 44 print(boss.__str__())

 in __init__(self, name, power, agility, HP)
 27 class Boss(Worrior,Archer):
 28      def __init__(self, name, power, agility, HP):
---> 29      Worrior.__init__(self, name, power, HP)
 30          Archer.__init__(self, name, agility, HP)
 31      def __str__(self):

 in __init__(self, name, power, HP)
  7 class Worrior(Player):
  8     def __init__(self, name, power, HP):
----> 9     super().__init__(HP)
 10         self.name = name
 11         self.power = power

TypeError: __init__() missing 2 required positional arguments: 'agility' and 'HP'

It seems after taking the power attribute inside the Worrior class, and then stop.

class Player:
def __init__(self,HP):
    self.HP = HP 
def sign_in(self):
    print('player sign in')
# put the class want to extend from

class Worrior(Player):
def __init__(self, name, power, HP):
    super().__init__(HP)
    self.name = name
    self.power = power
# it's the toString() method in java
# need to override the dunder(magic) method
def __str__(self):
    return "The worrior's name: " f'{self.name} \n' \
        "He has the power:" f'{self.power}'

class Archer(Player):
def __init__(self, name, agility, HP):
    super().__init__(HP)
    self.name = name
    self.agility = agility
def __str__(self):
    return "The archer's name: " f'{self.name} \n' \
        "He has the agility:" f'{self.agility}'

class Boss(Worrior,Archer):
def __init__(self, name, power, agility, HP):
     Worrior.__init__(self, name, power, HP)
     Archer.__init__(self, name, agility, HP)
def __str__(self):
    return "The boss's name: " f'{self.name} \n' \
        "With Worrior's power " f'{self.power} \n' \
        "and With Archer's agilit" f'{self.agility}'\
        "The boss' HP is: " f'{self.HP}'
boss = Boss("Boss", 50, 50, 100)
print(boss.__str__())
Woden
  • 1,054
  • 2
  • 13
  • 26
  • 1
    Have a look at https://stackoverflow.com/questions/3277367/how-does-pythons-super-work-with-multiple-inheritance – Thierry Lathuille Aug 07 '20 at 05:37
  • 1
    @ThierryLathuille that addresses order and is a very good post, but it does not address how to deal with cases where the signature of the constructor changes from class to class (like it does here) – Grismar Aug 07 '20 at 05:39

3 Answers3

3

The link from @Thierry Lathuille is the correct one to read, but I'll try to add some extra explanation. The MRO for the initializer is [Boss, Worrior, Archer, Player]. What this means (somewhat confusingly) is that when Worrior calls super(), this is actually referring to Archer, not Player. If you place print(super().__init__) before each time you call the method, you will find output like this prior to your crash:

<bound method Worrior.__init__ of <__main__.Boss object at 0x10af5f780>>
<bound method Archer.__init__ of <__main__.Boss object at 0x10af5f780>>
Traceback (most recent call last):
...

This is the major pitfall IMHO with multiple inheritance in Python, and I generally advise against it unless you have zero-argument initializers.

If you try to explicitly call each base class initializer (e.g. Player.__init__), then you will end up with some of your initializers executing multiple times.

In order to be able to pass your arguments through, you'll need to leverage **kwargs. Additionally, as these get passed through, each unique argument name will get stripped from the kwargs. So this means that you can't reuse name as an initialization argument. I'll use worrior_name and archer_name instead. So putting this all together, the following will run each initializer only once, without crashing:

class Player:
    def __init__(self, hp, **kwargs):
        print(super().__init__)
        self.hp = hp

    def sign_in(self):
        print('player sign in')


class Worrior(Player):
    def __init__(self, worrior_name, power, **kwargs):
        super().__init__(**kwargs)
        self.name = worrior_name
        self.power = power

    def __str__(self):
        return "The worrior's name: " f'{self.name} \n' \
               "He has the power:" f'{self.power}'


class Archer(Player):
    def __init__(self, archer_name, agility, **kwargs):
        super().__init__(**kwargs)
        self.name = archer_name
        self.agility = agility

    def __str__(self):
        return "The archer's name: " f'{self.name} \n' \
               "He has the agility:" f'{self.agility}'


class Boss(Worrior, Archer):
    def __init__(self, name, power, agility, hp):
        super().__init__(archer_name=name, worrior_name=name, power=power, agility=agility, hp=hp)

    def __str__(self):
        return "The boss's name: " f'{self.name} \n' \
               "With Worrior's power " f'{self.power} \n' \
               "and With Archer's agilit" f'{self.agility}' \
               "The boss' hp is: " f'{self.hp}'
Dane White
  • 3,443
  • 18
  • 16
  • *"you can't reuse `name` as an initialization argument. I'll use `worrior_name` and `archer_name` instead"* -- Wouldn't it make more sense to move the `name` parameter to `Player.__init__`? – wjandrea Aug 20 '23 at 16:05
1

This is happening because of the method resolution order (MRO) in new style classes. MRO of the Boss class -

ipdb> Boss.mro()                                                                                                                   
[<class '__main__.Boss'>, <class '__main__.Worrior'>, <class '__main__.Archer'>, <class '__main__.Player'>, <class 'object'>]
ipdb>  

This means that super() calls in Worrior class is actually referring to Archer, not Player.

You can refer to this stack overflow post - Method Resolution Order (MRO) in new-style classes?, which explains nicely how MRO works in python.

Tabaene Haque
  • 576
  • 2
  • 10
0

It appears that Python decides to walk the entire hierarchy again when it arrives at super() in the Warrior class (I've corrected your spelling, it's not 'Worrior').

I'm not sure where you're going wrong or whether this is just a situation where you cannot use super(). I expected the same as you and was surprised by the result as well.

However, the code below doesn't have the problem, although you of course lose the flexibility and I don't like having to do this:

class Player:
    def __init__(self, hp):
        self.hp = hp

    def sign_in(self):
        print('player sign in')


class Warrior(Player):
    def __init__(self, name, power, hp):
        Player.__init__(self, hp)
        self.name = name
        self.power = power

    def __str__(self):
        return "The warrior's name: " f'{self.name} \n' \
            "He has the power:" f'{self.power}'


class Archer(Player):
    def __init__(self, name, agility, hp):
        Player.__init__(self, hp)
        self.name = name
        self.agility = agility

    def __str__(self):
        return "The archer's name: " f'{self.name} \n' \
            "He has the agility:" f'{self.agility}'


class Boss(Warrior, Archer):
    def __init__(self, name, power, agility, hp):
        Warrior.__init__(self, name, power, hp)
        Archer.__init__(self, name, agility, hp)

    def __str__(self):
        return "The boss's name: " f'{self.name} \n' \
            "With Warrior's power: " f'{self.power} \n' \
            "and With Archer's agility: " f'{self.agility}'\
            "The boss' HP is: " f'{self.hp}'


boss = Boss("Boss", 50, 50, 100)
print(boss.__str__())

So, not really a complete answer, but wanted to provide the feedback - hope someone else provides a complete answer with an explanation of what is really going on.

Grismar
  • 27,561
  • 4
  • 31
  • 54
  • Thank you for reviewing the code @Grismar. Actually, it's safe and structured without multiple inheritances in my opinion. – Woden Aug 07 '20 at 05:40