Python 的新 `functools.cached_property` 错误或限制?

tho*_*len 5 python python-3.x

从 Python 3.8 开始,functools有一个cached_property. 我一直在使用lazyprop基于 Beazley 的食谱(下面的代码)的类似装饰器,但是当我用内置函数替换时,我遇到了问题。这是其中之一。

当我在类定义中使用装饰器时,使用@运算符,它不会抱怨。

但是如果我将它与 一起使用setattr,我会得到:

TypeError: Cannot use cached_property instance without calling __set_name__ on it.
Run Code Online (Sandbox Code Playgroud)

Beazley 的简单版本虽然很好用。

TypeError: Cannot use cached_property instance without calling __set_name__ on it.
Run Code Online (Sandbox Code Playgroud)

che*_*ner 6

TL;DR 缓存是实例字典本身,并且需要属性的名称作为键。所选择的设计施加了限制,但(IMO)这是一个很好的折衷方案。lazyprop不是线程安全的,或者至少可能调用self.func超出多线程环境中严格必要的调用。


首先,据记录,仅__set_name__当赋值发生在类型创建的上下文中时才会调用。文档中的注释(适合您的示例)显示了您需要执行的操作:给__set_name__自己打电话。

class D:
    def __init__(self, greeting='Hello'):
        self.greeting = greeting

    def greet(self):
        return self.greeting + " world!"

D.greet = cached_property(greet)
D.greet.__set_name__(D, 'greet')  # sets D.greet.attrname = "greet" for later use

assert D().greet == "hello world!"
Run Code Online (Sandbox Code Playgroud)

为什么需要属性名称?cached_property.__set__未定义,因此给定d = D(),d.greet将首先查找名为 的实例greet属性d.__dict__。如果没有找到,那么D.greet.__get__(d, D)就会被调用。该函数基本上做了三件事:如果需要计算值,它调用原始greet函数,然后将其保存到具有相同名称的新实例属性,然后返回计算值。

“等等”,你会问,“ ‘如果需要’是什么意思?你刚才不是说D.greet.__get__只有实例属性不存在时才调用吗? ”是的,但是在多线程环境中,你不知道另一个线程是否也D.greet.__get__可能同时执行。为了防止竞争情况,__get__请执行以下步骤(如果您愿意,可以按照代码进行操作):

  1. 检查是否有同名的实例属性,以防它是在当前调用开始后在另一个线程中创建的。
  2. 如果没有,请尝试获取锁,以便我们可以自己创建实例属性。
  3. 一旦获得锁,请再次查找实例属性,以防在我们等待锁时有人创建了它。
  4. 如果实例属性仍然不存在,我们可以安全地自己创建它。
  5. 最后,无论我们从实例属性中提取值还是greet自己调用原始函数,都可以返回该值。

考虑到所有这些,我认为这是一个限制,而不是一个错误,而是一个很容易解决的限制。此实现可能比尝试不依赖属性本身的名称来维护必要的映射的实现更简单。