为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

摇曳的蔷薇 2022-12-20 12:24:41
从 Python 3.8 开始,functools有一个cached_property. 我一直在使用lazyprop基于 Beazley 的食谱(下面的代码)的类似装饰器,但是当我用内置函数替换时,我遇到了问题。这是其中之一。当我在类定义中使用装饰器时,使用@运算符,它不会抱怨。但是,如果我将它与 一起使用setattr,我会得到:TypeError: Cannot use cached_property instance without calling __set_name__ on it.Beazley 的简单版本虽然工作正常。from functools import cached_propertyclass lazyprop:    """Based on code from David Beazley's "Python Cookbook"."""    def __init__(self, func):        self.__doc__ = getattr(func, '__doc__')        self.func = func    def __get__(self, instance, cls):        if instance is None:            return self        else:            value = instance.__dict__[self.func.__name__] = self.func(instance)            return valueclass D:    def __init__(self, greeting='Hello'):        self.greeting = greetingdef greet(self):    return self.greeting + " world!"# Beazley's version works...D.greet = lazyprop(greet)assert D().greet == "Hello world!"# ... but the builtin version will failD.greet = cached_property(greet)# D().greet  # this will fail
查看完整描述

2 回答

?
元芳怎么了

TA贡献1798条经验 获得超7个赞

缓存是实例字典本身,并且需要属性的名称作为键。所选设计施加了限制,但(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!"

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


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

  1. 检查具有相同名称的实例属性,以防它是在当前调用开始后在另一个线程中创建的。

  2. 如果没有,请尝试获取锁,以便我们可以自己创建实例属性。

  3. 获得锁后,再次查找实例属性,以防在我们等待锁时有人创建了它。

  4. 如果实例属性仍然不存在,我们可以安全地自己创建它。

  5. 最后,无论我们从实例属性中拉取一个值还是我们自己调用原始greet函数,我们都可以返回该值。

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


查看完整回答
反对 回复 2022-12-20
?
呼唤远方

TA贡献1856条经验 获得超11个赞

这既不是错误也不是限制。Using__set_name__只是推断属性名称的一种不同方式——与常规类语法一起使用时更可靠。


例如,__set_name__也适用于仅cached_property直接绑定到名称的匿名函数:


from functools import cached_property

import random


class Bar:

    foo = cached_property(lambda self: random.random())


bar = bar()

print(bar.foo)  # 0.9901613829744336

print(bar.foo)  # 0.9901613829744336

相反,当使用时cached_property.cached_property,值存储不当——即bar.<lambda>——阻止它隐藏属性。


>>> functools_bar.__dict__

{'foo': 0.9901613829744336}

>>> cached_property_bar.__dict__

{'<lambda>': 0.7003011051281254}


查看完整回答
反对 回复 2022-12-20
  • 2 回答
  • 0 关注
  • 240 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号