我最近也在想同样的事情。我正在寻找一个更符合“Python 的实现会导致mro/__mro__ 未在dir 中列出吗?我可以相信dir 列出什么?”的答案。而不仅仅是“Python的文档如何证明mro不包含在dir中?”当我对编程语言行为的期望与它的实际行为不匹配时,我不喜欢它,因为这意味着我对该语言的理解是不正确的——除非它只是像urllib2.escape 这样的错误。所以我挖了一点,直到找到答案。
上述 cmets 文档中引用的 adolfopa 行很好地解释了 dir 的行为。
“如果对象是类型或类对象,则列表包含其属性的名称,并递归地包含其基类的属性。”
这是什么意思? dir 递归地从类的 __dict__ 及其每个超类的 __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
dir(object) 没有列出__mro__/mro 的原因是它们不是对象的属性。它们是type 的属性。每个没有定义它自己的__metaclass__ 的类都是type 的一个实例。大多数元类子类type。这种元类的实例同样是类型的实例。 MyClass.__mro__ 与 type.__getattribute__(MyClass,'__mro__') 相同。
Python 实现类的方式必然会对 dir 的工作方式产生轻微的反常。
通常是dir(MyClass) == dir(MyClass(*requiredparameters)) #True。
但是,dir(type) == dir(type(*requiredparameters)) #False,但唯一可能的情况是 type.__dict__ 和 dir 相同。这显然不是dir 的目的。
但是等等! dir 是由递归和创建的,为什么我们不能只更改它的最后一部分,使 dir(object) 不再只是 object.__dict__.keys() 而是变成 object.__dict__.keys() + type.__dict__.keys()。那样的话,它会有mro/__mro__ 以及类对象具有的所有其他属性吗?啊,但那些将是类对象的属性,而不是类。那么,有什么区别呢?
考虑
list.__mro__ #(<type 'list'>, <type 'object'>)
而
[].__mro__
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# AttributeError: 'list' object has no attribute '__mro__'
现在,我们可以回答我们可以依靠dir 列出哪些内容以及我们可以依靠dir 不列出哪些内容。简单的答案是我们已经介绍过的答案。它递归地列出了类__dict__ 中的所有键以及每个超类__dict__ 中的所有键。对于一个实例,它还包括实例的__dict__ 中的所有参数。要填充负数,它不会列出在__getattr__ 中定义的任何内容,或者在__getattribute__ 中定义的任何内容(如果它不在__dict__ 中)。它也没有列出类型/元类型的任何属性。
我觉得我应该指出的另一件事是:Dan 的答案,在我写这篇文章时是公认的答案,其中包含不准确或至少具有误导性的信息。
无法设置内置对象的属性,因此从某种意义上说,type.__mro__ 是“只读的”,但仅与list.append 或type.mro 相同。
MyClass.__mro__ = "Hello world!" 不会导致错误。它根本不会影响type 中定义的方法解析顺序。因此,如果您尝试修改该行为,它可能不会产生您期望的效果。 (它的作用是使MyClass(*requiredparameters).__mro__ 成为"Hello World!",这应该是您所期望的,因为这是在python 中定义类的属性的方式。)您也可以覆盖__mro__ 当您是子-分类类型来创建一个元类。如果你不覆盖它,它就会被继承,就像你不覆盖的任何其他东西一样。 (如果您要创建的元类不是类型的子类,也不是返回类型实例的函数,那么您大概已经清楚地知道自己做得很好,因此您不必担心这一点,但是__mro__ 不会被继承,因为您没有继承type)
根据文档(描述 super 的行为):
该类型的 __mro__ 属性列出了 getattr() 和 super() 使用的方法解析搜索顺序。该属性是动态的,并且可以在继承层次结构更新时更改。
因此,直接修改 __mro__ 的行为应该符合您的预期,与修改 mro 的方式大致相同。但是,通过覆盖函数通常更容易获得所需的行为。 (想想你需要做什么才能正确处理子类化。)