在Python中缓存类属性
我正在用 python 编写一个类,并且有一个属性需要相对较长的时间来计算,因此我只想执行一次。此外,并非该类的每个实例都需要它,因此我不想在 __init__
中默认执行此操作。
我是 Python 新手,但不是编程新手。我可以很容易地想出一种方法来做到这一点,但我一遍又一遍地发现,“Pythonic”的做事方式通常比我利用其他语言的经验想出的方法要简单得多。
在 Python 中是否有“正确”的方法来做到这一点?
I'm writing a class in python and I have an attribute that will take a relatively long time to compute, so I only want to do it once. Also, it will not be needed by every instance of the class, so I don't want to do it by default in __init__
.
I'm new to Python, but not to programming. I can come up with a way to do this pretty easily, but I've found over and over again that the 'Pythonic' way of doing something is often much simpler than what I come up with using my experience in other languages.
Is there a 'right' way to do this in Python?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
3.8≤Python
@property
和@functools.lru_cache
已合并到@cached_property
。3.2≤Python< 3.8
您应该同时使用
@property
< /a> 和@functools.lru_cache
装饰器:这个答案有更详细的示例,还提到了以前的 Python 版本的向后移植。
Python 3.2
Python wiki 有一个 缓存属性装饰器(MIT 许可),可以像这样使用:
或者其他答案中提到的任何适合您需求的实现。
或者上面提到的向后移植。
3.8 ≤ Python
@property
and@functools.lru_cache
have been combined into@cached_property
.3.2 ≤ Python < 3.8
You should use both
@property
and@functools.lru_cache
decorators:This answer has more detailed examples and also mentions a backport for previous Python versions.
Python < 3.2
The Python wiki has a cached property decorator (MIT licensed) that can be used like this:
Or any implementation mentioned in the others answers that fits your needs.
Or the above mentioned backport.
我曾经按照 gnibbler 的建议这样做,但最终我厌倦了这些小家务步骤。
所以我构建了自己的描述符:
以下是使用它的方法:
I used to do this how gnibbler suggested, but I eventually got tired of the little housekeeping steps.
So I built my own descriptor:
Here's how you'd use it:
通常的方法是使属性成为 property 并首先存储值计算的时间
The usual way would be to make the attribute a property and store the value the first time it is calculated
Python 3.8 包含
functools.cached_property
装饰器。此示例直接来自文档:
限制是具有要缓存的属性的对象必须具有作为可变映射的
__dict__
属性,排除具有__slots__
的类除非__dict__
是在__slots__
中定义的。Python 3.8 includes the
functools.cached_property
decorator.This example is straight from the docs:
The limitation being that the object with the property to be cached must have a
__dict__
attribute that is a mutable mapping, ruling out classes with__slots__
unless__dict__
is defined in__slots__
.如前所述,functools.cached_property 适用于缓存的实例属性。对于缓存的class属性:
3.9 <= python < 3.13
如果你想要一个可重用的装饰器:
python >= 3.13
在 3.13 中,链接
classmethod
和property
是不允许的,所以你会必须使用元类或自定义装饰器,这是只读缓存属性的示例:或者自定义装饰器:
As mentioned,
functools.cached_property
will work for cached instance attributes. For cached class attributes:3.9 <= python < 3.13
And if you want a reusable decorator:
python >= 3.13
In 3.13 chaining
classmethod
andproperty
is disallowed so you will have to use metaclasses or a custom decorator, here's an example of a read-only cached attribute:Or alternatively a custom decorator:
dickens
包(不是我的)提供了cachedproperty
、classproperty
和cachedclassproperty
装饰器。缓存类属性:
The
dickens
package (not mine) offerscachedproperty
,classproperty
andcachedclassproperty
decorators.To cache a class property:
您可以尝试研究记忆。它的工作方式是,如果您向函数传递相同的参数,它将返回缓存的结果。您可以在此处找到有关在 Python 中实现它的更多信息。
另外,根据您的代码的设置方式(您说并非所有实例都需要它),您可以尝试使用某种享元模式或延迟加载。
You could try looking into memoization. The way it works is that if you pass in a function the same arguments, it will return the cached result. You can find more information on implementing it in python here.
Also, depending on how your code is set up (you say that it is not needed by all instances) you could try to use some sort of flyweight pattern, or lazy-loading.
大多数(如果不是全部)当前答案都是关于缓存实例属性。要缓存类属性,您可以简单地使用字典。这确保了每个类计算一次属性,而不是每个实例计算一次。
为了说明,
给出
Most if not all current answers are about caching instance attributes. To cache class attributes, you can simply use a dictionary. This ensures the attributes are calculated once per class, instead of once per instance.
To illustrate,
gives
最简单的方法可能是只编写一个包装属性的方法(而不是使用属性)(getter 方法)。第一次调用时,该方法计算、保存并返回值;之后它只返回保存的值。
The most simple way of doing this would probably be to just write a method (instead of using an attribute) that wraps around the attribute (getter method). On the first call, this methods calculates, saves and returns the value; later it just returns the saved value.
对于 Python 2,而不是 Python 3,这就是我所做的。这大约是您可以获得的最高效率:
说明:基本上,我只是用计算值重载属性方法。因此,在您第一次访问该属性(对于该实例)后,
foo
不再是一个属性,而是成为一个实例属性。这种方法的优点是缓存命中尽可能便宜,因为 self.__dict__ 被用作缓存,并且如果不使用该属性,则没有实例开销。此方法不适用于 Python 3。
With Python 2, but not Python 3, here's what I do. This is about as efficient as you can get:
Explanation: Basically, I'm just overloading a property method with the computed value. So after the first time you access the property (for that instance),
foo
ceases to be a property and becomes an instance attribute. The advantage of this approach is that a cache hit is as cheap as possible becauseself.__dict__
is being used as the cache, and there is no instance overhead if the property is not used.This approach doesn't work with Python 3.