在 Python 中,为什么属性优先于实例属性?

In Python, why do properties take priority over instance attributes?

This article 描述了 Python 在执行 o.a 时如何查找对象的属性。优先顺序很有趣 - 它寻找:

  1. 作为数据描述符的 class 属性(最常见的是 属性)
  2. 实例属性
  3. 任何其他 class 属性

我们可以使用下面的代码来确认这一点,它创建了一个对象 o 和一个实例属性 a,其 class 包含一个同名的 属性 :

class C:
    def __init__(self):
        self.__dict__['a'] = 1

    @property
    def a(self):
        return 2

o = C()
print(o.a)  # Prints 2

为什么 Python 使用此优先顺序而不是 "naive" 顺序(实例属性优先于所有 class 属性)? Python 的优先级顺序有一个明显的缺点:它使属性查找变慢,因为 Python 必须首先搜索,而不是只返回 o 的属性(如果存在) o 的 class 及其所有超级classes 用于数据描述符。

Python 的优先顺序有什么好处?大概不仅仅是上述情况,因为有一个实例变量和一个同名的 属性 是非常极端的情况(注意需要使用 self.__dict__['a'] = 1 来创建实例属性,因为通常 self.a = 1 会调用 属性).

是否存在 "naive" 查找顺序会导致问题的不同情况?

Guido van Rossum 本人(Python 的前 BDFL)在 2001 年 Python 2.2 引入新样式 类 时设计了此功能。讨论了推理在 PEP 252。明确提到对属性查找的影响:

This scheme has one drawback: in what I assume to be the most common case, referencing an instance variable stored in the instance dict, it does two dictionary lookups, whereas the classic scheme did a quick test for attributes starting with two underscores plus a single dictionary lookup.

并且:

A benchmark verifies that in fact this is as fast as classic instance variable lookup, so I'm no longer worried.

What I want to know is, why do data descriptors take priority over instance attributes?

如果没有优先于正常实例查找的方法,您希望如何拦截实例属性访问?这些方法本身不能是实例属性,因为那样会破坏它们的目的(我认为至少没有关于它们的额外约定)。

除了@timgeb 的简明评论,关于描述符的任何解释我都比不上官方Descriptor How To

对于旧样式 类 (PEP 252):

The instance dict overrides the class dict, except for the special attributes (like __dict__ and __class__), which have priority over the instance dict.

覆盖实例字典中的 __dict____class__ 会破坏属性查找并导致实例以极其奇怪的方式运行。

在新式 类 中,Guido 选择了以下属性查找实现来保持一致性 (PEP 252):

  1. Look in the type dict. If you find a data descriptor, use its get() method to produce the result. This takes care of special attributes like __dict__ and __class__.
  2. Look in the instance dict. If you find anything, that's it. (This takes care of the requirement that normally the instance dict overrides the class dict.)
  3. Look in the type dict again (in reality this uses the saved result from step 1, of course). If you find a descriptor, use its get() method; if you find something else, that's it; if it's not there, raise AttributeError.

总之,__dict____class__ 属性被实现为属性(数据描述符)。为了保持有效状态,实例字典不能覆盖 __dict____class__。因此,属性(数据描述符)优先于实例属性。