pythoninheritancesuperdiamond-problem

Calling constructors of both parents in multiple inheritance in Python (general case)


I'm trying to figure out the multiple inheritance in Python, but all articles I find are limited to simple cases. Let's consider the following example:

class Vehicle:
    def __init__(self, name: str) -> None:
        self.name = name
        print(f'Creating a Vehicle: {name}')

    def __del__(self):
        print(f'Deleting a Vehicle: {self.name}')


class Car(Vehicle):
    def __init__(self, name: str, n_wheels: int) -> None:
        super().__init__(name)
        self.wheels = n_wheels
        print(f'Creating a Car: {name}')

    def __del__(self):
        print(f'Deleting a Car: {self.name}')


class Boat(Vehicle):
    def __init__(self, name: str, n_props: int) -> None:
        super().__init__(name)
        self.propellers = n_props
        print(f'Creating a Boat: {name}')

    def __del__(self):
        print(f'Deleting a Boat: {self.name}')


class Amfibii(Car, Boat):
    def __init__(self, name: str, n_wheels: int, n_props: int) -> None:
        Car.__init__(self, name, n_wheels)
        Boat.__init__(self, name, n_props)
        print(f'Creating an Amfibii: {name}')

    def __del__(self):
        print(f'Deleting an Amfibii: {self.name}')


my_vehicle = Amfibii('Mazda', 4, 2)

I want to understand the order of calling constructors and destructors, as well as the correct and general use of the 'super' keyword. In the example above, I get the following error:

super().__init__(name) TypeError: Boat.__init__() missing 1 required positional argument: 'n_props'

How should I correctly call constructors of both parents, which have different sets of constructor arguments?


Solution

  • Thanks to the comments and the following article, I understood it. https://rhettinger.wordpress.com/2011/05/26/super-considered-super/

    1. Regarding the constructors' arguments, they should be named. In this way, every level strips what it needs and passes it to the next one using super()

    2. Every class calls super().__init__() only once. The following classes in the MRO order are responsible for continuing the initialization.

    The updated example:

    class Vehicle:
        def __init__(self, name: str) -> None:
            self.name = name
            print(f'Creating a Vehicle: {name}')
    
        def go(self):
            print('Somehow moving...')
            assert not hasattr(super(), 'go')
    
    
    class Car(Vehicle):
        def __init__(self, n_wheels: int,  **kwargs) -> None:
            super().__init__(**kwargs)
            self.wheels = n_wheels
            print(f'Creating a Car: {self.name}')
    
        def go(self):
            print('Riding...')
            super().go()
    
    
    class Boat(Vehicle):
        def __init__(self, n_props, **kwargs) -> None:
            super().__init__(**kwargs)
            self.propellers = n_props
            print(f'Creating a Boat: {self.name}')
    
        def go(self):
            print('Swimming...')
            super().go()
    
    
    class Amfibii(Car, Boat):
        def __init__(self, **kwargs) -> None:
            super().__init__(**kwargs)
            print(f'Creating an Amfibii: {self.name}')
    
        def go(self):
            print('Riding or swimming...')
            super().go()
    
    
    my_vehicle = Amfibii(name='Mazda', n_wheels=4, n_props=2)
    my_vehicle.go()
    

    It's a bit cumbersome (gently speaking), isn't it?

    EDIT: I updated the code according co the comments.