首页 > 解决方案 > 让@property 只运行一次

问题描述

我有以下示例:

class A:
    value = None
    @property
    def value(self):
        if not value:
            result = <do some external call>
            self.value = result
        return self.value

但是,这不起作用,因为我遇到了异常:

AttributeError: can't set attribute

这是有道理的,但是做这样的事情的惯用方式是什么?我不只是想使用不同的名称。

标签: python

解决方案


您正在尝试将其self.value用作属性的名称,以及用于存储该属性的属性名称。那没有意义;你需要给它一个不同的名字。由于该属性是“私有的”,并且只能通过属性访问,因此您通常希望使用下划线前缀。

换句话说,做与文档property中的示例完全相同的事情。

当然,if not value:必须将其更改为通过 访问相同的属性self,而不是某些具有相同名称的局部变量。

class A:
    _value = None
    @property
    def value(self):
        if not self._value:
            result = <do some external call>
            self._value = result
        return self._value

另一种选择是使用为您执行此操作的库。正如 Martijn Pieters 在评论中指出的那样,cached-property它可以为您提供您想要的东西。它在您可能从未想过的各种边缘情况(例如线程和异步)中经过测试且稳健。它也有很好的文档和可读的源代码,解释了它是如何工作的。


无论如何,为什么这_value = None是正确的可能不是很明显。

_value = None在类定义中创建一个名为的类属性_value,由该类的所有实例共享。

self._value = result在方法主体中不会更改该类属性,它会创建一个具有相同名称的实例属性。

实例属性影子类属性。也就是说,当您尝试访问and语句self._value时,它会查找实例属性,如果不存在则回退到类属性。因此,类属性的值作为实例属性的默认值。ifreturnNone

对于提供默认属性值,这是一个非常常见的习惯用法——但在某些情况下(例如,使用可变值)可能会造成混淆。

做同样事情的另一种方法是通过在您的or方法_value中设置它来强制实例始终具有一个属性:__new____init__

class A:
    def __init__(self):
        self._value = None
    @property
    def value(self):
        if not self._value:
            result = <do some external call>
            self._value = result
        return self._value

虽然乍一看这有点冗长和复杂(并且需要了解__init__),但它确实避免了混淆类与实例属性的可能性,因此如果您与新手共享代码,他们更有可能会能够跟随它。

或者,您可以使用getattr来查看属性是否存在,因此您不需要后备类属性或__init__,但这通常不是您想要做的。


退后一步,您甚至可以property使用在第一次查找时替换自身的描述符进行替换——但如果您不知道这意味着什么(即使在阅读了HowTo之后),您可能也不想这样做。


推荐阅读