How MRO, super works in python

How MRO, super works in python



Can someone help me understand how MRO works in python?
Suppose I have four classes - Character, Thief, Agile, Sneaky. Character is the super class to Thief, Agile and Sneaky are siblings. Please see my code and question below


class Character:
def __init__(self, name="", **kwargs):
if not name:
raise ValueError("'name' is required")
self.name = name

for key, value in kwargs.items():
setattr(self, key, value)


class Agile:
agile = True

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


class Sneaky:
sneaky = True

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


class Thief(Agile, Sneaky, Character):
def pickpocket(self):
return self.sneaky and bool(random.randint(0, 1))


parker = Thief(name="Parker", sneaky=False)



So, here is what I think is going on, please let me know if I'm understanding it correctly.



Since Agile is first on the list, all arguments are first sent to Agile where the arguments will be cross-referenced with the Agile parameters. If there is a match the value will be assigned, then everything that doesn't have a matching keyword will be packed up in *kwargs and sent to the Sneaky class (via super), where the same thing will happen - all arguments get unpacked, cross-referenced with the Sneaky parameters (this is when sneaky = False is set), then packed up in kwargs and sent to Character. Then everything within the Character inint method will run and all values will be set (like the name = "Parker").



HOW I THINK MRO WORKS ON THE WAY BACK



Now that everything made it to the Character class and everything in the Character init method has run, now it has to go back to the Agile and Sneaky classes and finishing running everything in their init methods(or everything under their super). So, it will first go back to the Sneaky class and finish it's init method, then go back to the Agile class and finish the rest of its init method (respectively).



Do I have it confused anywhere? Phew. I'm sorry, I know this is a lot, but I'm really stuck here and I'm trying to get a clear understanding of how MRO works.



Thank you, everyone.





This code doesn't run as posted. There's at least one indentation error, the classes are out of order, … That means it's impossible to debug your code to help answer your questions.
– abarnert
Aug 25 at 3:05





@ReblochonMasque Actually, it makes perfect sense for mixins meant to be used in this kind of cooperative multiple inheritance to call super. After all, the only way any method will ever get to Sneaky is from Agile calling super.
– abarnert
Aug 25 at 3:06


super


Sneaky


Agile


super





@ReblochonMasque I don't know of anything specific, because it's sort of combining two separate ideas that are usually complicated enough to have their own separate tutorials. Usually anyone explaining cooperative MI with super is going to focus examples on diamond hierarchies, like this one, and anyone explaining mixins, like this answer, if they go beyond the basics, is probably going to go on to classes that are simultaneously a mixin and an ABC, like the collections.abcs…
– abarnert
Aug 25 at 4:24


super


collections.abc





@ReblochonMasque But the two concepts fit together fine. A mixin that's intended to be used with a cooperative hierarchy, if it needs to override __init__ (or anything else), will have to super it even though it doesn't inherit anything. (Of course that means the mixin can only work with that hierarchy, but that's fine; you're not going to use a werkzeug.AcceptMixin with non-werkzeug classes even without any super.
– abarnert
Aug 25 at 4:26


__init__


super


werkzeug.AcceptMixin


super





@ReblochonMasque And actually, that just brought up an example: most of the werkzeug mixin methods aren't overriding anything that needs to be chained up, but some are, like ETagResponseMixin.freeze, and they use super to do that. This means ETagResponseMixin can only be used in a hierarchy where the ultimate base class (or at least some ancestor) has a freeze method that it can super, but that's fine, because it's not intended to be used anywhere else.
– abarnert
Aug 25 at 4:28


ETagResponseMixin.freeze


super


ETagResponseMixin


freeze


super




1 Answer
1



Your code as posted doesn't even compile, much less run. But, guessing at how it's supposed to work…



Yes, you've got things basically right.



But you should be able to verify this yourself, in two ways. And knowing how to verify it may be even more important than knowing the answer.



First, just print out Thief.mro(). It should look something like this:


Thief.mro()


[Thief, Agile, Sneaky, Character, object]



And then you can see which classes provide an __init__ method, and therefore how they'll be chained up if everyone just calls super:


__init__


super


>>> [cls for cls in Thief.mro() if '__init__' in cls.__dict__]
[Agile, Sneaky, Character, object]



And, just to make sure Agile really does get called first:


Agile


>>> Thief.__init__
<function Agile.__init__>



Second, you can run your code in the debugger and step through the calls.



Or you can just add print statements at the top and bottom of each one, like this:


print


def __init__(self, agile=True, *args, **kwargs):
print(f'>Agile.__init__(agile=agile, args=args, kwargs=kwargs)')
super().__init__(*args, **kwargs)
self.agile = agile
print(f'<Agile.__init__: agile=agile')



(You could even write a decorator that does this automatically, with a bit of inspect magic.)


inspect



If you do that, it'll print out something like:


> Agile.__init__(agile=True, args=(), kwargs='name': 'Parker', 'sneaky':False)
> Sneaky.__init__(sneaky=False, args=(), kwargs='name': 'Parker')
> Character.__init__(name='Parker', args=(), kwargs=)
< Character.__init__: name: 'Parker'
< Sneaky.__init__: sneaky: False
< Agile.__init__: agile: True



So, you're right about the order things get called via super, and the order the stack gets popped on the way back is obviously the exact opposite.


super



But, meanwhile, you've got one detail wrong:



sent to the Sneaky class (via super), where the same thing will happen - all arguments get unpacked, cross-referenced with the Sneaky parameters (this is when sneaky = False is set)



This is where the parameter/local variable sneaky gets set, but self.sneaky doesn't get set until after the super returns. Until then (including during Character.__init__, and similarly for any other mixins that you choose to throw in after Sneaky), there is no sneaky in self.__dict__, so if anyone were to try to look up self.sneaky, they'd only be able to find the class attribute—which has the wrong value.


sneaky


self.sneaky


super


Character.__init__


Sneaky


sneaky


self.__dict__


self.sneaky



Which raises another point: What are those class attributes for? If you wanted them to provide default values, you've already got default values on the initializer parameters for that, so they're useless.



If you wanted them to provide values during initialization, then they're potentially wrong, so they're worse than useless. If you need to have a self.sneaky before calling Character.__init__, the way to do that is simple: just move self.sneaky = sneaky up before the super() call.


self.sneaky


Character.__init__


self.sneaky = sneaky


super()



In fact, that's one of the strengths of Python's "explicit super" model. In some languages, like C++, constructors are always called automatically, whether from inside out or outside in. Python forcing you to do it explicitly is less convenient, and harder to get wrong—but it means you can choose to do your setup either before or after the base class gets its chance (or, of course, a little of each), which is sometimes useful.


super





Thank you for the detailed explanation. I creating this code while following along with an online python course. I'm not sure why the instructor has me making class attributes and declaring them in the initializer parameters. Hopefully, he explains why in the next few steps. I'll relay that question to their form to see if I can get a good answer. There are a lot of new terms for me in your answer so I have some studying to do. Thanks again.
– Philip Schultz
Aug 25 at 7:57






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)