How Is __mro__ Different From Other Double Underscore Names?
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?"