考虑以下内容:
@property
def name(self):
if not hasattr(self, '_name'):
# expensive calculation
self._name = 1 + 1
return self._name
我是新手,但我认为缓存可以拆分成一个装饰器。只是我没有找到类似的装饰器;)
PS:真正的计算不依赖于可变值
考虑以下内容:
@property
def name(self):
if not hasattr(self, '_name'):
# expensive calculation
self._name = 1 + 1
return self._name
我是新手,但我认为缓存可以拆分成一个装饰器。只是我没有找到类似的装饰器;)
PS:真正的计算不依赖于可变值
从Python 3.2开始,有一个内置的装饰器:
@functools.lru_cache(maxsize=100, typed=False)
该装饰器用于将函数包装为一个记忆可调用对象,最多保存最近的maxsize个调用。当一个昂贵或I/O绑定的函数被周期性地使用相同的参数调用时,它可以节省时间。
计算斐波那契数列的LRU缓存示例:
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
>>> print([fib(n) for n in range(16)])
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]
>>> print(fib.cache_info())
CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)
lru_cache
复制它缓存的任何结果,而在 functools.lru_cache
实现中并没有进行这样的复制。这样做也会有可能在用于缓存大对象时产生难以找到的内存问题。 - gerritPython 3.8 functools.cached_property
装饰器
https://docs.python.org/dev/library/functools.html#functools.cached_property
cached_property
是由Werkzeug提供的,它在这里被提到:https://dev59.com/u3RA5IYBdhLWcg3wzhbZ#5295190 ,但一种推测派生版本将会合并到3.8中,这很棒。
这个装饰器可以看作是缓存了@property
,或者当你没有任何参数时,可以看作是一个更清晰的@functools.lru_cache
。
文档说:
@functools.cached_property(func)
将类中的某个方法转换为属性,该属性的值仅计算一次,并缓存为实例生命周期内的普通属性。与property()类似,但增加了缓存功能。对于昂贵的计算实例属性而言非常有用,这些实例属性在其他方面实际上是不可变的。
示例:
class DataSet: def __init__(self, sequence_of_numbers): self._data = sequence_of_numbers @cached_property def stdev(self): return statistics.stdev(self._data) @cached_property def variance(self): return statistics.variance(self._data)
版本 3.8 中新增。
注意:该装饰器要求每个实例上的 dict 属性是可变映射。这意味着它无法与某些类型一起使用,例如元类(因为类型实例上的 dict 属性是类命名空间的只读代理),以及那些指定 slots 但未将 dict 包含在定义的 slot 中的类型(此类不提供任何 dict 属性)。
functools.cache
已在 Python 3.9 中发布 (文档)。
from functools import cache
@cache
def factorial(n):
return n * factorial(n-1) if n else 1
在之前的Python版本中,其中一个早期回答仍然是有效的解决方案:使用lru_cache
作为普通缓存,没有限制和LRU特性。(文档)
如果将maxsize设置为None,则禁用LRU功能,缓存可以无限增长。
这里有一个更漂亮的版本:
cache = lru_cache(maxsize=None)
@cache
def func(param1):
pass
for _ in range(1500): factorial(496)
这个花费了0.0941
而仅缓存变量时需要0.0003
... 我本以为这将被缓存以加速整个文件的迭代,而不仅仅是在一个程序循环期间,唉... - Yu Da Chi听起来你不是在寻求一个通用的记忆化装饰器(即,你对于缓存不同参数值的返回值的一般情况不感兴趣)。也就是说,你想要这样的东西:
x = obj.name # expensive
y = obj.name # cheap
而一般用途的记忆化装饰器则会给你这个:
x = obj.name() # expensive
y = obj.name() # cheap
我认为方法调用语法是更好的风格,因为它暗示了可能存在昂贵的计算,而属性语法则暗示了一个快速查找。
[更新:我之前链接和引用的基于类的记忆化装饰器不适用于方法。我已经用一个装饰器函数替换了它。] 如果你愿意使用通用的记忆化装饰器,这里有一个简单的:
def memoize(function):
memo = {}
def wrapper(*args):
if args in memo:
return memo[args]
else:
rv = function(*args)
memo[args] = rv
return rv
return wrapper
使用示例:
@memoize
def fibonacci(n):
if n < 2: return n
return fibonacci(n - 1) + fibonacci(n - 2)
在这里可以找到另一个具有缓存大小限制的记忆化装饰器,链接为此处。
fibonacci
的调用的相同函数。该函数始终使用相同的 memo
字典。 - Nathan Kitchenclass memorize(dict):
def __init__(self, func):
self.func = func
def __call__(self, *args):
return self[args]
def __missing__(self, key):
result = self[key] = self.func(*key)
return result
使用示例:
>>> @memorize
... def foo(a, b):
... return a * b
>>> foo(2, 4)
8
>>> foo
{(2, 4): 8}
>>> foo('hi', 3)
'hihihi'
>>> foo
{(2, 4): 8, ('hi', 3): 'hihihi'}
__missing__
中会引发TypeError错误:缺少1个必需的位置参数:'self'
。 - jl005我编写了这个简单的装饰器类来缓存函数的响应。我发现它对我的项目非常有用:
from datetime import datetime, timedelta
class cached(object):
def __init__(self, *args, **kwargs):
self.cached_function_responses = {}
self.default_max_age = kwargs.get("default_cache_max_age", timedelta(seconds=0))
def __call__(self, func):
def inner(*args, **kwargs):
max_age = kwargs.get('max_age', self.default_max_age)
if not max_age or func not in self.cached_function_responses or (datetime.now() - self.cached_function_responses[func]['fetch_time'] > max_age):
if 'max_age' in kwargs: del kwargs['max_age']
res = func(*args, **kwargs)
self.cached_function_responses[func] = {'data': res, 'fetch_time': datetime.now()}
return self.cached_function_responses[func]['data']
return inner
使用方法很简单:
import time
@cached
def myfunc(a):
print "in func"
return (a, datetime.now())
@cached(default_max_age = timedelta(seconds=6))
def cacheable_test(a):
print "in cacheable test: "
return (a, datetime.now())
print cacheable_test(1,max_age=timedelta(seconds=5))
print cacheable_test(2,max_age=timedelta(seconds=5))
time.sleep(7)
print cacheable_test(3,max_age=timedelta(seconds=5))
@cached
缺少括号。否则它只会返回 cached
对象而不是 myfunc
,当作为 myfunc()
调用时,inner
将始终作为返回值被返回。 - Markus Meskanen免责声明:我是kids.cache的作者。
你应该查看kids.cache
,它提供了一个@cache
装饰器,适用于Python 2和Python 3。没有依赖项,代码只有大约100行。使用起来非常简单,例如,考虑到你的代码,你可以像这样使用它:
pip install kids.cache
那么
from kids.cache import cache
...
class MyClass(object):
...
@cache # <-- That's all you need to do
@property
def name(self):
return 1 + 1 # supposedly expensive calculation
或者您可以在@property
之后放置@cache
装饰器(结果相同)。
在属性上使用缓存称为惰性评估,kids.cache
可以做更多的事情(它适用于具有任何参数、属性、任何类型的方法甚至类的函数...)。对于高级用户,kids.cache
支持cachetools
,该工具提供了Python 2和Python 3的花式缓存存储(LRU、LFU、TTL、RR缓存)。
重要提示:默认的kids.cache
缓存存储是标准字典,不建议在长时间运行的程序中使用,因为它会导致不断增长的缓存存储。对于此用途,您可以使用其他缓存存储插件,例如(@cache(use=cachetools.LRUCache(maxsize=2))
来装饰您的函数/属性/类/方法...)
MyClass
的实例 c
,并使用 objgraph.show_backrefs([c], max_depth=10)
进行检查,可以看到从类对象 MyClass
到 c
有一个引用链。也就是说,在 MyClass
被释放之前,c
永远不会被释放。 - Timothy Zhangfrom joblib import Memory
memory = Memory(cachedir=cachedir, verbose=0)
@memory.cache
def f(x):
print('Running f(%s)' % x)
return x