0

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.

1 Answers1

2

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")
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • When you say don't cooperate, you mean using `ABC` module? –  Feb 14 '19 at 16:20
  • @EmilyScott: no, I mean that the base classes also use `super()` to pass on the call chain. – Martijn Pieters Feb 14 '19 at 16:20
  • This is the second time where useful information was available at PyCon, and I feel like I'm missing out. This goes beyond the scope of the question, but when you can't attend, how do you keep up with that goes on at PyCon? –  Feb 14 '19 at 16:30
  • You see how you were able to do `super().__init__(name=name, damage=damage, amount=amount)`, with out calling `Reloadable` to pass in amount, where does it say you can do that in the blog post or presentation? –  Feb 14 '19 at 16:47
  • 1
    @EmilyScott: using keyword arguments makes the *the caller and callee need to have a matching argument signature* requirement much easier; the `**kwargs` catch-all makes each `__init__` implementation 'tolerant' of extra arguments being passed down the chain. – Martijn Pieters Feb 14 '19 at 16:59
  • 1
    @EmilyScott: as Raymond writes: *A more flexible approach is to have every method in the ancestor tree cooperatively designed to accept keyword arguments and a keyword-arguments dictionary, to remove any arguments that it needs, and to forward the remaining arguments using `**kwds`, eventually leaving the dictionary empty for the final call in the chain.* – Martijn Pieters Feb 14 '19 at 17:00
  • 1
    @EmilyScott: `Sniper()` here accepts `name`, `damage`, and `amount` as expliictly named arguments, so we need to pass those on to the base classes, and we do so here by passing them on as keyword arguments. – Martijn Pieters Feb 14 '19 at 17:01
  • 1
    @EmilyScott: as for tracking PyCon: just about every talk is recorded and put on YouTube, often within hours of being recorded. Check the schedule for a given year, pick the talks that you think might be interesting, and look them up on YouTube. E.g. look at the [2018 schedule](https://us.pycon.org/2018/schedule/talks/), then plug in a title with 'pycon 2018' in to your favourite search engine and it'll lead you to the talk. – Martijn Pieters Feb 14 '19 at 17:02
  • Huge thanks for the help and answers in the comments. Thanks for the schedule resource! –  Feb 14 '19 at 17:03
  • I asked a followup [question](https://stackoverflow.com/questions/54735138/when-using-the-abc-module-are-keyword-arguments-a-good-practice), but no one seems to want to answer. Maybe you can provide some insight. –  Feb 17 '19 at 19:43