Python Object-Oriented Python Inheritance Multiple Superclasses

Philip Schultz
Philip Schultz
11,322 Points

What is happening when we give the Thief class sneaky = False.

Hey everyone, At 3:45 , what is happening exactly, how are all of the classes communicating? He creates an instance of the Thief class and he gives it a 'name' argument and the sneaky = False argument. I understand what is happening with the name argument, I think (going to the Character class), but where is the argument 'sneaky = False' going? Is it somehow making its way to the Sneaky class? If so, How? Why didn't the Character class or the Agile class say "ok that is a kwarg send it to setattr method". I'm assuming that means that Python is checking to see if there are class variables that equal key/word argument before using it, right? Also, what happens if you do give a kwarg or an arg? I feel like it would just default to the character class, because according to MRO it would be the first in line, but if that is the case then why have args and kwargs in the attribute classes at all?

Another question, why are attribute Classes using the super function...do they have a parent class? Are the attribute classes considered parents to the Thief class? Do you use the super function whenever you want to allow other classes to access the class, parent or not?

Also, he ends up changing what he does at 3:45 because he says that Python 2 would error out. Are developers constantly trying to accommodate for previous versions? If this is the case should everyone learn Python 2 first so you know the limitation and differences? It seems counter productive to make newer and better versions of a language, but designing your code to accommodate older versions.

thanks

1 Answer

Chris Freeman
MOD
Chris Freeman
Treehouse Moderator 59,039 Points

Great question!

The classes are not specifically “communicating”, per se, but parts of each class is being executed depending on the Method Resolution Order (MRO), or the order of the inherited classes, along with the use of the super() function.

Let’s assume an inheritance order of Agile, Sneaky, and Character. The execution order starts with Agile.__init__(), then due to the super statement, execution moves to Sneaky.__init__(). From here, the super() statement sents the execution on to Character.__init__(). Note that the variables args and kwargs are local to each __init__ function.

By using *args and **kwargs, any argument passed in that doesn’t have an explicitly matching parameter defined will be placed in the appropriate extra argument containers. Since “sneaky” isn’t explicitly defined in Agile.__init__(), the argument is placed in the **kwargs container and passed to Sneaky.__init__(). The argument is explicitly defined in Sneaky so it isn’t placed in the new **kwargs. When Character.__init__() executes **kwargs is empty and *args contains name.

The attribute classes do not have a parent class. If the definition of the attribute class did have a parent class listed in the class definition then that parent’s __init__ would be called by the super().__init__(). Since there is not a parent class listed, the super() shifts execution to the next class in the Thief’s MRO. super() is only used to access the next parent or class in the MRO. The attribute classes are considered parents to the Thief class.

Sometimes projects are written using code that would run in both Python 2 and 3. This can be done by restricting the coding to a common subset of both Python versions, but loses out on some of the unique richness available in either version. Python 3 will be the future and Treehouse focuses primarily on Python 3. I suggest also focusing on Python 3 first. Once you’ve mastered Python 3, learning the parts that differ from Python 2 will be straightforward. So it’s OK to leave Python 2 for now.

Post back if you need more help. Good luck!!!

So if our inheritance order is (Agile, Sneaky, Character) if we created an instance of Thief Class:

Noor = Thief(name="Noor", sneaky=False)

1- first init to be called is (Agile init) , so our instance acquires agile attribute and it defaults to True since we haven't override it in our instance initialization , then name="Noor" and Sneaky=False gets packed in our **kwargs container and since we put (*args, **kwargs) in our super() function , sneaky=False get's carried to the second init on our MRO

2- when second init called (Sneaky init), **kwargs container contains sneaky=False and gets passed by *arg to (Sneaky init) and our instance acquires sneaky attribute and it gets set to False since we we override it's default in our passed kwarg then name="Noor" gets packed again to **kwargs container and passed to the next class in MRO chain by super() function

3-at last (Character init) get called and name="Noor" gets unpacked from **kwarg and passed as *arg and our instance acquires it's third and last attribute which is (name atrribute) and since we override name's default empty string "" , it get set as "Noor"

so now our instance have 3 attributes : Noor.agile == True // Noor.sneaky == False // Noor.name == "Noor"

Chris Freeman did i get it right here , or there is something not correct on the way of how i understand inheritance here

one last thing if we assume our inits don't have super() functions , it will still call all inits in inheritance chain one by one , the super() function here just carries *args and **kwargs down the MRO if they exists.

Chris Freeman
Chris Freeman
Treehouse Moderator 59,039 Points

Noor Elharty, you have it correct! One slight correction would be that the first __init__ to run would be Theif.__init__() of course, if it exists, then as you said: Agile.__init__(), Sneaky.__init__(), and finally, Character.__init__().

Regarding your last comment "...if we assume our inits don't have super() functions , it will still call all inits in inheritance chain one by one , the super() function here just carries *args and **kwargs down the MRO if they exists."

If the __init__ methods do not have super() function, then execution will stop at the first __init__ without a super() call. This is why in the inheritance order (Character, Agile, Sneaky) execution will not pass to either Agile.__init__() or Sneaky.__init__().

Chris Freeman so now if i understand correctly , the way MRO work in general . it looks for methods on it's chain and as soon as it find this method , the MRO stops there even if the next classes on the chain contains the same method ( I think to prevent any conflict ) back to our example , what super() do here is that it allow us to carry bits of code from first __init__ method down to the last __init__ method as long as every __init__ on the way down contains the same super() with the same parameters.

and sorry if i bothered you with my questions here , i'm trying to get a good grasp about the way MRO and super() works , OOP isn't the easiest thing to grasp..

Chris Freeman
Chris Freeman
Treehouse Moderator 59,039 Points

No bother at all. Though I'm not on TTH every day, I do eventually check in on every forum mention I get. I also try to look at any new activity on posts I'm following.

If you are curious to see the MRO on any object you can use object.__class__.__mro__