Why does __mro__ not show up in dir(MyClass)?

馋奶兔 提交于 2019-12-03 03:53:22

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.

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!