Python Object-Oriented Python Inheritance Multiple Superclasses

How is super() being used

import random

class Sneaky:
    sneaky = True

    def __init__(self, sneaky=True, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.sneaky = sneaky

    def hide(self, light_level):
        return self.sneaky and light_level < 10

class Agile:
    agile = True

    def __init__(self, agile=True, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.agile = agile

    def evade(self):
        return self.agile and random.randint(0, 1)

Can you explain how super().init(*args, **kwargs) is functioning here for both Sneaky and Agile? I thought it's only used when there's a superclass within the same script.

2 Answers

Jeff Muday
MOD
Jeff Muday
Treehouse Moderator 23,731 Points

The short answer to this is what he did has no effect, (yet). But as it comes into play in multiple inheritance, it does have an effect of passing along parameters during the multiple initialization methods.

The super() is a way to reference a method in the parent class. Since neither Agile nor Sneaky have parent classes (well... technically speaking the Python "object" class is the parent), but there's nothing that happens when super().init() is executed in Agile and Sneaky if used in a single-inheritance way.

But the idea of super() is important, that the child class gets the benefit of the inheritance of its parent's class and then can overide methods and data of the parent as well as add on its own methods.

The real takeaway from this video is that Kenneth is demonstrating Multiple inheritance and MRO (Method Resolution Order) The MRO can be demonstrated by changing the order of Character, Sneaky, and Agile classes when he defines a new object based on those. His design decision is that the Character class is the "base class" and Sneaky and Agile are "attribute classes" and the Thief class he is building inherits from Character, Sneaky, and Agile. If the order is changed, it can change which parent methods are passed on to the child.

so here in this video when we create an instance of Thief Class.

The first init to be called is "Sneaky" init because on the top of MRO for the Thief Class is Thief Class itself , and since Thief class doesn't have init method The MRO falls back to the next class in it's chain of commands which is "Sneaky" class which have init method so "Sneaky" init called First and our instance acquires (sneaky=True attribute) , then Agile init called second and our instance acquires (agile=True attribute) , then Character init called last and our instance acquires (name attribute) what super() function do here is that it gets *args and *kwargs code all the way down from Character class (Sneaky supers Agile and Agile supers Character)

did i get it right ? i know i said much , but i hope you can validate my understanding of inheritance here

Jeff Muday
MOD
Jeff Muday
Treehouse Moderator 23,731 Points

To me, what you said sounds correct. Here is an example that uses the object method called "mro()" which stands for multiple resolution order. The developer can choose the order that makes sense.

Here is an example below. We start with a generic Character class which cannot pick locks by default. The Sneaky characteristic is a class that can pick a lock with a skill. We will not work with Agile class, but it is there because that was Kenneth's example.

Picking locks---

The Thief class is both Sneaky and Agile but at its heart a Character, when a Thief picks a lock, it uses "awesome dexterity" as its method.

The Magician is defined as a Character first and Sneaky second. So "Character" class takes precedence and overirdes the Sneaky pick_lock() -- despite the Magician having "magic spells" it can't use these to pick a lock since Character class comes first in the order for pick_lock() which overrides and blocks the Sneaky.pick_lock().

class Character:
    @classmethod
    def pick_lock(self):
        return "I can't pick locks with %s" % (self.skill)

class Sneaky:
    @classmethod
    def pick_lock(self):
        return "I can pick a lock with %s" % (self.skill)

class Agile:
    pass

class Thief(Sneaky, Agile, Character):
    skill = "awesome dexterity"

class Magician(Character, Sneaky):
    skill = "magic spells"

# show the MRO (Method Resolution Order of each class)
print( Character.mro() )
print( Sneaky.mro() )
print( Agile.mro() )
print( Thief.mro() )
print( Magician.mro() )

# Now... pick a lock

print("Thief: " + Thief.pick_lock()) # Thief succeeds.

print("Magician: " + Magician.pick_lock()) # Magician fails, because of MRO

Look at the MRO outputs to see the order is resolved.

Picking locks... the Thief can pick a lock with "awesome dexterity", but because a Magician is defined as Character which overrides the Sneaky ability to pick locks. So his "magic spells" do not work.

[<class '__main__.Character'>, <class 'object'>]
[<class '__main__.Sneaky'>, <class 'object'>]
[<class '__main__.Agile'>, <class 'object'>]
[<class '__main__.Thief'>, <class '__main__.Sneaky'>, <class '__main__.Agile'>, <class '__main__.Character'>, <class 'object'>]
[<class '__main__.Magician'>, <class '__main__.Character'>, <class '__main__.Sneaky'>, <class 'object'>]
Thief: I can pick a lock with awesome dexterity
Magician: I can't pick locks with magic spells