class MyClass(object):
pass
print MyClass.__mro__
print dir(MyClass)
Output:
(<class '__main__.MyClass'>, <type 'object'>)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
Why is __mro__
not listed with dir()
?
I was recently wondering the same thing. I was looking for an answer more in line with "What about Python's implementation causes mro
/__mro__
not to be listed in dir
? And what can I trust dir
to list?" than simply "How does Python's documentation justify the non-inclusion of mro
in dir
?" I don't like it when my expectation of a programming language's behavior does not match it's actual behavior because it means my understanding of the language is incorrect -- unless it's just a bug like urllib2.escape
. So I dug around a little bit, until I figured out the answer.
The line adolfopa quoted from the documentation in the comments above is a good one for explaining the behavior of dir.
"If the object is a type or class object, the list contains the names of its attributes, and recursively of the attributes of its bases."
What does this mean? dir
recursively gathers attributes from a class's __dict__
and each of its superclasses' __dict__
.
set(dir(object)) == set(dict(object.__dict__).keys() #True
class A(object):
...
class B(object):
...
class C(B):
...
class D(C,A):
...
set(dir(D)) == set(D.__dict__.keys()) + set(C.__dict__.keys()) \
+ set(B.__dict__.keys()) + set(A.__dict__.keys()) \
+ set(object.__dict__.keys()) #True
The reason dir(object)
does not list __mro__
/mro
is that they are not attributes of object. They are attributes of type
. Every class that doesn't define it's own __metaclass__
is an instance of type
. Most metaclasses subclass type
. Instances of such metaclasses are likewise instances of type. MyClass.__mro__
is the same as type.__getattribute__(MyClass,'__mro__')
.
The way Python implements classes necessarily creates a slight anomaly with respect to how dir works.
Typically, dir(MyClass) == dir(MyClass(*requiredparameters)) #True
.
However, dir(type) == dir(type(*requiredparameters)) #False
, but the only way this could possibly be otherwise was if type.__dict__
and dir
were the same. This is, quite self-evidently, not the purpose of dir
.
But wait! dir
is created by a recursive sum, why can't we just change the final piece of it so that dir(object)
is no longer just object.__dict__.keys()
but rather it becomes object.__dict__.keys() + type.__dict__.keys()
. That way, it would have mro
/__mro__
and all of the other attributes that the class object has? Ah, but those would be attributes of the class object, not the class. Well, what is the difference?
Consider
list.__mro__ #(<type 'list'>, <type 'object'>)
Whereas
[].__mro__
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# AttributeError: 'list' object has no attribute '__mro__'
Now, we are in a good place to answer what we can count on dir
to list and what we can count on dir
to not list. The simple answer is the one we already covered. It lists all the keys in the class's __dict__
plus all the keys in each of its superclasses' __dict__
's, recursively. For an instance, it also includes all the parameter's in the instance's __dict__
. To fill in the negative space, it doesn't list anything defined in __getattr__
or in anything in __getattribute__
if it isn't also in the __dict__
. It also doesn't list any attributes of the type/metatype.
One other thing that I feel I should point out: Dan's answer, which is the accepted answer at the time I am writing this, contains information that is inaccurate or at least misleading.
Attributes of built in objects cannot be set, so in a sense, type.__mro__
is 'read-only,' but only in the same way that list.append
is or type.mro
is, for that matter.
MyClass.__mro__ = "Hello world!"
does not cause an error. It simply does not affect the method resolution order defined in type
. So it may not have the effect you were expecting if you were attempting to modify that behavior . (What it does do is cause MyClass(*requiredparameters).__mro__
to be "Hello World!"
which should have been what you would have expected, since this is how defining attributes of classes works in python.) You can also override __mro__
when you are sub-classing type to create a metaclass. If you don't override it, it is inherited, just like anything else that you don't override. (If you are creating a metaclass that isn't a subclass of type and that isn't a function that returns an instance of type, presumably, you already know exactly what you are doing well enough that you aren't worrying about this, but __mro__
would not be inherited since your not subclassing type
)
According to the docs (describing the behavior of super):
The __mro__ attribute of the type lists the method resolution search order used by both getattr() and super(). The attribute is dynamic and can change whenever the inheritance hierarchy is updated.
So directly modifying __mro__
should behave as you would expect, in much the same way that modifying mro
would. However, it's typically easier to get the behavior you want by overriding the function. (Think about what you need to do to handle subclassing properly.)
From the Python documentation:
Because dir() is supplied primarily as a convenience for use at an interactive prompt, it tries to supply an interesting set of names more than it tries to supply a rigorously or consistently defined set of names, and its detailed behavior may change across releases. For example, metaclass attributes are not in the result list when the argument is a class.
__mro__
is a read-only attribute that's used to determine method resolution in case your class inherits from multiple base classes. If you wish to customize this behavior, you should use a metaclass (a special kind of object whose purpose is to create class instances) which overrides the mro()
method. __mro__
is left unchanged in any case.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With