Skip to content Skip to sidebar Skip to footer

How Is __mro__ Different From Other Double Underscore Names?

I stumbled upon this behavior for double underscore name that I don't understand: class A: pass class B: pass class C(A,B): __id__ = 'c' c = C() print(C.__mro__) #

Solution 1:

This has to do with the lookup order.

Letting descriptors aside, python first checks the objects __dict__ to find an attribute. If it cannot find it, it will look at the class of the object and the bases of the class to find the attribute. If it cannot be found there either, AttributeError is raised.

This is probably not understandable, so let us show this with a short example:

#!/usr/bin/python3classFoo(type):
    X = 10classBar(metaclass=Foo):
    Y = 20

baz = Bar()

print("X on Foo", hasattr(Foo, "X")) 
print("X on Bar", hasattr(Bar, "X")) 
print("X on baz", hasattr(baz, "X")) 

print("Y on Foo", hasattr(Foo, "Y")) 
print("Y on Bar", hasattr(Bar, "Y")) 
print("Y on baz", hasattr(baz, "Y")) 

The output is:

X on Foo True
X on Bar True
X on baz False
Y on Foo False
Y on Bar True
Y on baz True

As you can see, X has been declared on the metaclassFoo. It is accessible through the instance of the metaclass, the class Bar, but not on the instance baz of Bar, because it is only in the __dict__ in Foo, not in the __dict__ of Bar or baz. Python only checks one step up in the "meta" hierarchy.

For more on metaclass magic, see the excellent answers on the question What is a metaclass in python?.

This, however, is not sufficient to describe the behaviour, because __mro__ is different for each instance of Foo (that is, for each class).

This can be achieved using descriptors. Before the attribute name is looked up at the objects __dict__, python checks the __dict__ of the class and its bases to see if there is a descriptor object assigned to the name. A descriptor is any object which has a __get__ method. If that is the case, the descriptor objects __get__ method is called and the result is returned from the attribute lookup. With a descriptor assigned to an attribute of the metaclass, the behaviour seen can be achieved: The descriptor can return a different value based on the instance argument, but nevertheless the attribute can only be accessed through the class and the metaclass, not instances of the class.

A prime example of descriptors is property. Here is a simple example with a descriptor which has the same behaviour as __mro__:

classDescriptor:
   def__get__(self, instance, owner):
      return"some value based on {}".format(instance)


classOtherFoo(type):
   Z = Descriptor()

classOtherBar(metaclass=OtherFoo):
   pass

other_baz = OtherBar()

print("Z on OtherFoo", hasattr(OtherFoo, "Z"))
print("Z on OtherBar", hasattr(OtherBar, "Z"))
print("Z on other_baz", hasattr(other_baz, "Z"))

print("value of Z on OtherFoo", OtherFoo.Z)
print("value of Z on OtherBar", OtherBar.Z)

The output is:

Z on OtherFoo True
Z on OtherBar True
Z on other_baz False
value of Z on OtherFoo some value based on None
value of Z on OtherBar some value based on <class'__main__.OtherBar'>

As you can see, OtherBar and OtherFoo both have the Z attribute accessible, but other_baz does not. Still, Z can have a different value for each OtherFoo instance, that is, each class using the OtherFoo metaclass.

Metaclasses are confusing at first, and even more so when descriptors are in play. I suggest reading up on metaclasses the linked question, as well as descriptors in python in general.

Post a Comment for "How Is __mro__ Different From Other Double Underscore Names?"