我最近也在想同样的事情。我正在寻找一个更符合“Python 的实现原因mro
/__mro__
不被列出的原因dir
?我可以信任dir
列出什么?”的答案。而不仅仅是“Python 的文档如何证明不包含mro
in是合理的dir
?” 当我对编程语言行为的期望与它的实际行为不匹配时,我不喜欢它,因为这意味着我对该语言的理解是不正确的——除非它只是一个像urllib2.escape
. 所以我挖了一点,直到我想出答案。
上面评论中的文档中引用的 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
。但是,通过覆盖函数通常更容易获得所需的行为。(想想你需要做什么来正确处理子类化。)