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

Posted

技术标签:

【中文标题】在 Python 中,为啥属性优先于实例属性?【英文标题】:In Python, why do properties take priority over instance attributes?在 Python 中,为什么属性优先于实例属性? 【发布时间】:2019-05-17 00:01:43 【问题描述】:

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

    作为数据描述符的类属性(最常见的是属性) 实例属性 任何其他类属性

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

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

    @property
    def a(self):
        return 2

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

为什么 Python 使用这种优先顺序而不是“幼稚”顺序(实例属性优先于所有类属性)? Python 的优先级顺序有一个显着的缺点:它使属性查找变慢,因为不是只返回 o 的属性(如果存在)(常见情况),Python 必须首先搜索 o 的类 及其所有数据描述符的超类

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

是否存在“天真的”查找顺序会导致问题的不同情况?

【问题讨论】:

AFAICT javascript 使用“幼稚”的查找顺序并且工作正常。以下代码,等效于 Python 示例,打印 1: class C constructor() Object.defineProperty(this, 'a', value: 1); get a() return 2; var o = new C(); console.log(o.a); 我是否正确理解“为什么 Python 中存在数据描述符?”你的问题归结为什么?您是否知道您可以通过编写描述符类来编写自己的非数据描述符“属性”? @timgeb - AFAIK 数据描述符(同时具有 __get____set__ 的描述符)主要用于支持属性。我想知道的是,为什么数据描述符优先于实例属性? 我假设是因为这是确保您的 getter 逻辑运行的一种方式。 我是这样看的:数据描述符是默认情况。当我有一个__get__ 和一个__set__ 方法时,我希望这些钩子触发,而不管对象的__dict__ 中是否存在相关属性的细节。如果属性不在__dict__ 中,那么您希望getter 仅触发的特殊情况下使用非数据描述符。例如,非数据描述符非常适合 __get__ting 一次值,然后将其缓存在实例 __dict__ 中。 【参考方案1】:

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

这个方案有一个缺点:在我认为最常见的情况下,引用存储在实例 dict 中的实例变量,它会进行两次字典查找,而经典方案对以两个下划线开头的属性进行快速测试加上一个字典查找。

还有:

基准测试证明这实际上与经典实例变量查找一样快,所以我不再担心。

【讨论】:

【参考方案2】:

我想知道的是,为什么数据描述符优先于 实例属性?

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

除了@timgeb的简洁评论,我无法比官方Descriptor How To更好地解释描述符

【讨论】:

【参考方案3】:

对于旧式课程 (PEP 252):

实例字典覆盖类字典,但特殊属性(如__dict____class__)除外,它们优先于实例字典。

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

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

    查看类型字典。如果找到数据描述符,请使用其get() 方法生成结果。这会处理 __dict____class__ 等特殊属性。 查看实例字典。如果你发现了什么,就是这样。 (这照顾了通常实例字典覆盖类字典的要求。) 再次查看类型 dict(当然,实际上这使用了步骤 1 中保存的结果)。如果你找到一个描述符,使用它的 get() 方法;如果你找到别的东西,就是这样;如果不存在,请引发 AttributeError。

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

【讨论】:

以上是关于在 Python 中,为啥属性优先于实例属性?的主要内容,如果未能解决你的问题,请参考以下文章

为啥不在实例属性查找中搜索元类的属性?

Java面向对象编程 -5.2

如何解决“允许属性将优先于allowfullscreen”警告?

为啥允许向已经实例化的对象添加属性?

为啥正文中的 javascript 函数优先于头部中的函数?

朴素贝叶斯 (Weka) - 属性总数 x 实例总数 - 为啥不同?