0
class Animal:
    def __init__(self, species):
        self.species = species

    def display_species(self):
        print(f"Species: {self.species}")

class Mammal(Animal):
    def __init__(self, species, habitat):
        super().__init__(species)
        self.habitat = habitat

    def display_habitat(self):
        print(f"Habitat: {self.habitat}")

class Bird(Animal):
    def __init__(self, species, wingspan):
        super().__init__(species)
        self.wingspan = wingspan

    def display_wingspan(self):
        print(f"Wingspan: {self.wingspan}")

class Bat(Mammal, Bird):
    def __init__(self, species, habitat, wingspan, name):
        Mammal.__init__(self, species, habitat)
        Bird.__init__(self, species, wingspan)
        self.name = name

    def display_info(self):
        print("Bat Information:")
        self.display_species()
        self.display_habitat()
        self.display_wingspan()
        print(f"Name: {self.name}")

class Parrot(Bird):
    def __init__(self, species, wingspan, color):
        super().__init__(species, wingspan)
        self.color = color

    def display_info(self):
        print("Parrot Information:")
        self.display_species()
        self.display_wingspan()
        print(f"Color: {self.color}")

    # Creating an instance of the Bat class
    bat = Bat("Bat", "Cave", "1.2 meters", "Batty")
bat.display_info()

    # Creating an instance of the Parrot class
    parrot = Parrot("Parrot", "30 cm", "Green")
    parrot.display_info()

Output:

Traceback (most recent call last):
  File "<string>", line 54, in <module>
  File "<string>", line 29, in __init__`your text`
  File "<string>", line 11, in __init__
TypeError: Bird.__init__() missing 1 required positional argument: 'wingspan'
imxitiz
  • 3,920
  • 3
  • 9
  • 33
  • 1
    You need to learn to format your question properly. It's unreadable now. – Mark Ransom May 16 '23 at 03:53
  • 1
    the fundamental problem is that you are using `super`. If you *are* going to use `super`, then your class hierarchy needs to be designed around it. Since you don't actually use `super` in your class with multiple inheritance, then everything is going to break. Just don't use `super` in the other place and explicitly call the methods you want like you did in `Bat` – juanpa.arrivillaga May 16 '23 at 04:09
  • You can try to use the "combination mode", most of the hybrid integration can use the combination mode to change some – 911 May 16 '23 at 06:24
  • Does this answer your question? [Calling parent class \_\_init\_\_ with multiple inheritance, what's the right way?](https://stackoverflow.com/questions/9575409/calling-parent-class-init-with-multiple-inheritance-whats-the-right-way) – Jorge Luis May 16 '23 at 07:02
  • 1
    Please clarify your specific problem or provide additional details to highlight exactly what you need. As it's currently written, it's hard to tell exactly what you're asking. – Community May 16 '23 at 09:00
  • Read https://rhettinger.wordpress.com/2011/05/26/super-considered-super/. – chepner May 25 '23 at 00:54

1 Answers1

1

This is not how one is supposed to use multiple inheritance in Python. The language linearizes the inheritance tree for a class, so that one needs to call methods in the superclass, that is made through a single call, usually using the super() language built-in - and not trying to manually call the methods in any specific super-class, like you do in Bat.__init__.

That is made easier with the mechanism of "kweyword arguments", using a double-asterisk prefix to both pack and unpack arbitrary keyword arguments, allowing one method to convey parameters it doesn't even know about to the super-classes.

In your case, without this resource, the Mammal class would have to "know" at coding time that it might be in a class hierarchy along with "Bird", so it would have to recognize a wingspan parameter, which, if passed, would be conveyed upstream:


class Mammal(Animal):
    def __init__(self, species, habitat, wingspan=None):
        if wingspan is None:
            super().__init__(species)
        else:
            super().__init__(species, wingspan=wingspan)
            
        self.habitat = habitat

This would allow your Bat to function like it is declared, but with a single super call -

class Bat(Mammal, Bird):
    def __init__(self, species, habitat, wingspan, name):
        super().__init__(species=specicies, wingspan=wingspan, habitat=habitat)
        self.name = name

if the inheriting order where reversed, though, it is the Bird.__init__ that would have to know about the habitat parameter and convey it optionally.

Also note that one is better requiring passing around named argument, because maintaining a consistent order across many children classes will become a nightmare, really fast.

So - this works, but it is quite awkward. When using the resource of "kwargs" that I mentioned earlier (this is a conventional abreviation, and usually the parameter name for this parameter), the code can become easily maintainable and expandable to more child classes:

Here is the working code using "kwargs" and a single super call. (I removed the print methods for concision, they will work as they are in your code)

class Animal:
    def __init__(self, species):
        self.species = species

class Mammal(Animal):
    def __init__(self, *, habitat, **kwargs):
        super().__init__(**kwargs)
        self.habitat = habitat


class Bird(Animal):
    def __init__(self, *, wingspan, **kwargs):
        super().__init__(**kwargs)
        self.wingspan = wingspan


class Bat(Mammal, Bird):
    def __init__(self, *, name, **kwargs):
        super().__init__(**kwargs)
        self.name = name

    ...

class Parrot(Bird):
    def __init__(self, *, color, **kwargs):
        super().__init__(**kwargs)
        self.color = color
    ...
    

# Creating an instance of the Bat class
bat = Bat(species="Bat", habitat="Cave", wingspan="1.2 meters", name="Batty")
#bat.display_info()

# Creating an instance of the Parrot class
parrot = Parrot(species="Parrot", wingspan="30 cm", color="Green")
#parrot.display_info()


Note that with this approach, it would all work just the same if one would change Bat to inherit from Bird first - class Bat(Bird, Mammal): - as the extra parameters for each __init__ are black-boxed inside the variable keyword arguments.

I would provide a link to Python docs where they explain the keyword arguments feature, but I failed to locate the resource in there - it is analogous to arbitrary argument lists, as explained in sessiosn 4.8.4 and 4.8.5 here: https://docs.python.org/3/tutorial/controlflow.html#arbitrary-argument-lists

The idea is that whenever one writes a function call, an arbitrary mapping (usually a dict), can be unpacked in place as if all of the key/value pairs in that dictionary where typed as named arguments. I.e., in:

parameters = {"fruit": "banana", "quantity": 5}

create_foods(when="now", **parameters)

The function "create_foods" would perceive the arguments as if they where written like:

create_foods(when="now", fruit="banana", quantity=5)

(Note the usage of ": the dictionary to be expanded is a plain dictionary, which in Python means the keys are typed within quotes)

And the converse thing is the feature most used above: any named arguments a function does not "want" to know about can be catch in a single parameter, that is presented as a dictionary. So, for the above call, a function declared as:

def create_food(when=None, **kwargs):
    ...

Would receive the parameters for fruits and count packed as a dictionary in the kwargs parameter.

jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • 1
    For completeness, `Animal.__init__` itself should also accept arbitrary keyword arguments and call `super().__init__`. (No harm done if `Animal` is really a "root' class, but necessary if someone inherits from `Animal` *and* an unrelated class also designed for cooperative multiple inheritance.) – chepner May 25 '23 at 00:53