Search code examples
pythoninheritanceconstructorsuper

Trouble understanding super() when calling multiple parents


I've been doing research on Python 3 (my code sample uses 3.7.2) and how to properly use super() when a class inherits more than one class.

I've read this page, and this page and this article. I think the problem is that the SO links are for an older version of Python, while the article is for Python 3, but it's still confusing.

Suppose I had the following code (don't worry if you think that the relationship can be modeled better, this is just an example to illustrate my problem):

class Weapon:

    def __init__(self, name, damage):
        self.name = name
        self.damage = damage

class Reloadable:

    def __init__(self, amount):
        self.amount = amount

class Sniper(Weapon, Reloadable):

    def __init__(self, name, damage, amount, scope_type):
        super().__init__(name, damage)
        super().__init__(self, amount)
        self.scope_type = scope_type

    def adjust_scope(self):
        print("Adjusting my scope")

Main:

gun = Sniper("Standard Sniper", 10, 20, "small")
gun.adjust_scope()

print(Sniper.__mro__)

and the MRO:

(<class 'inheritnacewithsuper.Sniper'>, 
 <class 'inheritnacewithsuper.Weapon'>, 
 <class 'inheritnacewithsuper.Reloadable'>, <class 'object'>)

The code works and called the desired parent classes, but I want to make sure, when using Python 3.7, and super(), is doing super().__init__(name, damage) and super().__init__(self, amount), the correct way to initialize the parent constructors?

The article doesn't do that, instead it called the super() for only one class (RightPyramid(Square, Triangle)).

I just want to make sure I'm on the right track, and using proper practices.


Solution

  • super() requires your code cooperates. Your Weapon and Reloadable classes don't, so you actually don't want to use super() here. You'd call the unbound methods directly on those base classes:

    class Sniper(Weapon, Reloadable):
        def __init__(self, name, damage, amount, scope_type):
            Weapon.__init__(self, name, damage)
            Reloadable.__init__(self, amount)
            self.scope_type = scope_type
    

    Without super(), the __init__ methods are unbound so you need to pass in self explicitly.

    See super() considered super! by Python core developer Raymond Hettinger (or the Python conference presentation of the same name for a great overview how to use super() in a cooperative manner.

    To be fully cooperative, all classes in your hierarchy should pass on the super().<methodname>() calls in the chain of classes. With mix-in classes like Reloadable, you'd want to either use a base no-op class or handle errors when calling super().__init__(), or pass on arguments as keyword arguments, and have each __init__() method accept arbitrary keyword arguments to pass on again:

    class Weapon:
        def __init__(self, name, damage, **kwargs):
            self.name = name
            self.damage = damage
            # pass on any remaining arguments
            super().__init__(**kwargs)
    
    class Reloadable:    
        def __init__(self, amount, **kwargs):
            self.amount = amount
            # pass on any remaining arguments
            super().__init__(**kwargs)
    
    class Sniper(Weapon, Reloadable):    
        def __init__(self, name, damage, amount, scope_type):
            self.scope_type = scope_type
            super().__init__(name=name, damage=damage, amount=amount)
    
        def adjust_scope(self):
            print("Adjusting my scope")