【问题标题】:Why does __mro__ not show up in dir(MyClass)?为什么 __mro__ 没有出现在 dir(MyClass) 中?
【发布时间】:2024-01-06 12:54:01
【问题描述】:
class MyClass(object):
    pass

print MyClass.__mro__
print dir(MyClass)

输出:

(<class '__main__.MyClass'>, <type 'object'>)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

为什么__mro__ 没有与dir() 一起列出?

【问题讨论】:

    标签: python method-resolution-order


    【解决方案1】:

    我最近也在想同样的事情。我正在寻找一个更符合“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.appendtype.mro 相同。

    MyClass.__mro__ = "Hello world!" 不会导致错误。它根本不会影响type 中定义的方法解析顺序。因此,如果您尝试修改该行为,它可能不会产生您期望的效果。 (它的作用是使MyClass(*requiredparameters).__mro__ 成为"Hello World!",这应该是您所期望的,因为这是在python 中定义类的属性的方式。)您也可以覆盖__mro__ 当您是子-分类类型来创建一个元类。如果你不覆盖它,它就会被继承,就像你不覆盖的任何其他东西一样。 (如果您要创建的元类不是类型的子类,也不是返回类型实例的函数,那么您大概已经清楚地知道自己做得很好,因此您不必担心这一点,但是__mro__ 不会被继承,因为您没有继承type)

    根据文档(描述 super 的行为):

    该类型的 __mro__ 属性列出了 getattr() 和 super() 使用的方法解析搜索顺序。该属性是动态的,并且可以在继承层次结构更新时更改。

    因此,直接修改 __mro__ 的行为应该符合您的预期,与修改 mro 的方式大致相同。但是,通过覆盖函数通常更容易获得所需的行为。 (想想你需要做什么才能正确处理子类化。)

    【讨论】:

    • 这部分似乎是错误的:“没有列出实例的任何属性的键......任何属于其中之一的东西,都不会被返回。”如果我这样做:class K(object): passinst=K()dir(inst) 然后 inst.attr=1 和另一个 dir(inst), attr 出现在第二个 dir 输出中(在 2.7.5 和 3.2.5 中)。大概您的意思是将讨论限制在dir(K) 而不是dir(inst)
    • 你是对的。感谢您指出我的错误。我会改正的。
    【解决方案2】:

    来自 Python 文档:

    因为提供 dir() 主要是为了方便在 交互式提示,它尝试提供一组有趣的名称 不仅仅是它试图提供一个严格或一致定义的集合 名称,其详细行为可能会因版本而异。为了 例如,当元类属性不在结果列表中时 参数是一个类。

    __mro__ 是一个只读属性,用于确定方法解析,以防您的类继承自多个基类。如果您希望自定义此行为,您应该使用覆盖mro() 方法的元类(一种特殊的对象,其目的是创建类实例)。 __mro__ 在任何情况下都保持不变。

    【讨论】:

    • 但是同一文档指出:“如果对象是类型或类对象,则列表包含其属性的名称,并递归地包含其基类的属性。”。这绝对令人困惑。
    • 因为提供 dir() 主要是为了方便在交互式提示下使用 - 它不是作为绝对完整性的工具。我承认这看起来很奇怪。
    • 在研究 Python 的工作原理时,一个更完整的 dir() 会更好。然而,当仅仅使用 Python 类和实例时,我们通常希望看到提供了哪些数据和服务,而不是它们是如何实现的。所以,虽然很奇怪,但日常实用性胜过 dir-must-show-all-details 的纯度。