What Does "Mro()" Do

What does mro() do?

Follow along...:

>>> class A(object): pass
...
>>> A.__mro__
(<class '__main__.A'>, <type 'object'>)
>>> class B(A): pass
...
>>> B.__mro__
(<class '__main__.B'>, <class '__main__.A'>, <type 'object'>)
>>> class C(A): pass
...
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
>>>

As long as we have single inheritance, __mro__ is just the tuple of: the class, its base, its base's base, and so on up to object (only works for new-style classes of course).

Now, with multiple inheritance...:

>>> class D(B, C): pass
...
>>> D.__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)

...you also get the assurance that, in __mro__, no class is duplicated, and no class comes after its ancestors, save that classes that first enter at the same level of multiple inheritance (like B and C in this example) are in the __mro__ left to right.

Every attribute you get on a class's instance, not just methods, is conceptually looked up along the __mro__, so, if more than one class among the ancestors defines that name, this tells you where the attribute will be found -- in the first class in the __mro__ that defines that name.

TypeError: Cannot create a consistent method resolution order (MRO)

Your GameObject is inheriting from Player and Enemy. Because Enemy already inherits from Player Python now cannot determine what class to look methods up on first; either Player, or on Enemy, which would override things defined in Player.

You don't need to name all base classes of Enemy here; just inherit from that one class:

class GameObject(Enemy):
pass

Enemy already includes Player, you don't need to include it again.

How does mro, goto, and set_subname interact?

Your second option is not working because the sub you are using as a wrapper does not match the prototype of the inner sub. monkey_patch is not only used for methods, and this changes how some functions are parsed. In particular, Mojo::Util::steady_time has an empty prototype and is often called without using parenthesis.

*{"${class}::$k"} = Sub::Util::set_prototype(
Sub::Util::prototype( $patch{$k} ),
Sub::Util::set_subname(
"${class}::$k",
sub {
my $cr = Sub::Util::set_subname("${class}::$k", $patch{$k});
goto $cr;
}
)
);

The third construct is not working because you are using goto to remove the renamed wrapper sub from the call stack, leaving only the inner sub which has no name. This breaks next::method's ability to find the correct method name.

python multiple inheritance from different paths with same method name

super() will only ever resolve a single class type for a given method, so if you're inheriting from multiple classes and want to call the method in both of them, you'll need to do it explicitly. i.e.A.foo(self)

What are the differences between type() and isinstance()?

To summarize the contents of other (already good!) answers, isinstance caters for inheritance (an instance of a derived class is an instance of a base class, too), while checking for equality of type does not (it demands identity of types and rejects instances of subtypes, AKA subclasses).

Normally, in Python, you want your code to support inheritance, of course (since inheritance is so handy, it would be bad to stop code using yours from using it!), so isinstance is less bad than checking identity of types because it seamlessly supports inheritance.

It's not that isinstance is good, mind you—it's just less bad than checking equality of types. The normal, Pythonic, preferred solution is almost invariably "duck typing": try using the argument as if it was of a certain desired type, do it in a try/except statement catching all exceptions that could arise if the argument was not in fact of that type (or any other type nicely duck-mimicking it;-), and in the except clause, try something else (using the argument "as if" it was of some other type).

basestring is, however, quite a special case—a builtin type that exists only to let you use isinstance (both str and unicode subclass basestring). Strings are sequences (you could loop over them, index them, slice them, ...), but you generally want to treat them as "scalar" types—it's somewhat incovenient (but a reasonably frequent use case) to treat all kinds of strings (and maybe other scalar types, i.e., ones you can't loop on) one way, all containers (lists, sets, dicts, ...) in another way, and basestring plus isinstance helps you do that—the overall structure of this idiom is something like:

if isinstance(x, basestring)
return treatasscalar(x)
try:
return treatasiter(iter(x))
except TypeError:
return treatasscalar(x)

You could say that basestring is an Abstract Base Class ("ABC")—it offers no concrete functionality to subclasses, but rather exists as a "marker", mainly for use with isinstance. The concept is obviously a growing one in Python, since PEP 3119, which introduces a generalization of it, was accepted and has been implemented starting with Python 2.6 and 3.0.

The PEP makes it clear that, while ABCs can often substitute for duck typing, there is generally no big pressure to do that (see here). ABCs as implemented in recent Python versions do however offer extra goodies: isinstance (and issubclass) can now mean more than just "[an instance of] a derived class" (in particular, any class can be "registered" with an ABC so that it will show as a subclass, and its instances as instances of the ABC); and ABCs can also offer extra convenience to actual subclasses in a very natural way via Template Method design pattern applications (see here and here [[part II]] for more on the TM DP, in general and specifically in Python, independent of ABCs).

For the underlying mechanics of ABC support as offered in Python 2.6, see here; for their 3.1 version, very similar, see here. In both versions, standard library module collections (that's the 3.1 version—for the very similar 2.6 version, see here) offers several useful ABCs.

For the purpose of this answer, the key thing to retain about ABCs (beyond an arguably more natural placement for TM DP functionality, compared to the classic Python alternative of mixin classes such as UserDict.DictMixin) is that they make isinstance (and issubclass) much more attractive and pervasive (in Python 2.6 and going forward) than they used to be (in 2.5 and before), and therefore, by contrast, make checking type equality an even worse practice in recent Python versions than it already used to be.

How MRO, super works in python

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, 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:

>>> [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:

>>> 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:

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.)

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.


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.


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.

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.



Related Topics



Leave a reply



Submit