请考虑以下事项:
@property
def name(self):
if not hasattr(self, '_name'):
# expensive calculation
self._name = 1 + 1
return self._name
我是新手,但我认为缓存可能会被装入装饰器中。只有我没有找到喜欢它的人;)
PS真正的计算不依赖于可变值
答案 0 :(得分:159)
从Python 3.2开始,有一个内置的装饰器:
@functools.lru_cache(maxsize=100, typed=False)
Decorator用一个memoizing callable来包装一个函数,它可以节省maxsize最近的调用。当使用相同的参数定期调用昂贵的或I / O绑定函数时,它可以节省时间。
用于计算Fibonacci numbers的LRU缓存示例:
@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)
如果你遇到Python 2.x,这里有一个其他兼容的memoization库列表:
functools32
| PyPI | Source code repoze.lru
| PyPI | Source code pylru
| PyPI | Source code backports.functools_lru_cache
| PyPI | Source code 答案 1 :(得分:26)
听起来你不要求使用通用的memoization装饰器(即,你不想对你想要为不同的参数值缓存返回值的一般情况感兴趣)。也就是说,你想拥有这个:
x = obj.name # expensive
y = obj.name # cheap
虽然通用的memoization装饰器会给你这个:
x = obj.name() # expensive
y = obj.name() # cheap
我认为方法调用语法是更好的样式,因为它提示了昂贵计算的可能性,而属性语法建议快速查找。
[更新:我之前链接到并引用的基于类的memoization装饰器不适用于方法。我用装饰器函数替换了它。]如果你愿意使用通用的memoization装饰器,这里有一个简单的:
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)
可以找到另一个对缓存大小有限制的memoization装饰器here。
答案 2 :(得分:20)
class 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'}
答案 3 :(得分:9)
答案 4 :(得分:7)
我编写了这个简单的装饰器类来缓存函数响应。我发现它对我的项目非常有用:
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))
答案 5 :(得分:6)
免责声明:我是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
或者您可以将@cache
装饰器放在@property
之后(相同的结果)。
在属性上使用缓存称为 lazy evaluation ,kids.cache
可以做更多的事情(它可以在函数上使用任何参数,属性,任何类型的方法,甚至类。 )。对于高级用户,kids.cache
支持cachetools
,它为python 2和python 3(LRU,LFU,TTL,RR缓存)提供精美的缓存存储。
重要提示:kids.cache
的默认缓存存储是一个标准的dict,不推荐用于长期运行的程序,因为它会导致不断增长的缓存存储。对于此用法,您可以使用例如@cache(use=cachetools.LRUCache(maxsize=2))
来插入其他缓存存储来装饰您的函数/属性/类/方法...)
答案 6 :(得分:5)
啊,只需找到正确的名称:“Lazy property evaluation”。
我也做了很多;也许我会在我的代码中使用那个食谱。
答案 7 :(得分:3)
如果您使用的是Django Framework,它具有缓存API视图或响应的属性
使用@cache_page(time)
,也可以有其他选项。
示例:
@cache_page(60 * 15, cache="special_cache")
def my_view(request):
...
可以找到更多详细信息here。
答案 8 :(得分:3)
在Python Wiki上还有另一个 memoize 装饰器的例子:
http://wiki.python.org/moin/PythonDecoratorLibrary#Memoize
这个例子有点聪明,因为如果参数是可变的,它不会缓存结果。 (检查代码,它非常简单有趣!)
答案 9 :(得分:2)
与Memoize Example一起,我找到了以下python包:
答案 10 :(得分:2)
有fastcache,它是“ Python 3 functools.lru_cache的C实现。与标准库相比提供了10-30倍的加速。”
与chosen answer相同,只是导入不同:
mf
此外,它与Anaconda的functools不同,它安装在needs to be installed中。
答案 11 :(得分:2)
import time
@cache(ttl=timedelta(minutes=3), max_entries=300)
def add(a, b):
time.sleep(2)
return a + b
@cache()
def substract(a, b):
time.sleep(2)
return a - b
a = 5
# function is called with argument combinations the first time -> it takes some time
for i in range(5):
print(add(a, i))
# function is called with same arguments again? -> will answer from cache
for i in range(5):
print(add(a, i))
from datetime import datetime, timedelta
def cache(**kwargs):
def decorator(function):
# static function variable for cache, lazy initialization
try: function.cache
except: function.cache = {}
def wrapper(*args):
# if nothing valid in cache, insert something
if not args in function.cache or datetime.now() > function.cache[args]['expiry']:
if 'max_entries' in kwargs:
max_entries = kwargs['max_entries']
if max_entries != None and len(function.cache) >= max_entries:
now = datetime.now()
function.cache = { key: function.cache[key] for key in function.cache.keys() if function.cache[key]['expiry'] > now }
# if nothing is expired that is deletable, delete the first
if len(function.cache) >= max_entries:
del function.cache[next(iter(function.cache))]
function.cache[args] = {'result': function(*args), 'expiry': datetime.max if 'ttl' not in kwargs else datetime.now() + kwargs['ttl']}
# answer from cache
return function.cache[args]['result']
return wrapper
return decorator
答案 12 :(得分:1)
我实现了类似的东西,使用pickle持久化并使用sha1来获得几乎肯定唯一的ID。基本上缓存散列函数的代码和参数的hist以获取sha1然后在名称中查找具有该sha1的文件。如果它存在,它打开它并返回结果;如果没有,它会调用该函数并保存结果(如果需要花费一定的时间来处理,则可选择保存)。
那就是说,我发誓我找到了一个现有的模块来做这个并发现自己在这里试图找到那个模块......我能找到的最接近的是这个,看起来是正确的:http://chase-seibert.github.io/blog/2011/11/23/pythondjango-disk-based-caching-decorator.html
我看到的唯一问题是它不适用于大型输入,因为它散列了str(arg),这对于巨型数组来说并不是唯一的。
如果有一个 unique_hash ()协议让一个类返回其内容的安全哈希,那就太好了。我基本上手动实现了我关心的类型。
答案 13 :(得分:1)
I A A2 B B2
1 'dog' 10 'cat' 20
2 'elf' 15 'egg' 45
3 'hat' 80 'bag' 50
不适用于默认函数值
我的@lru_cache
装饰者:
mem
和测试代码:
import inspect
def get_default_args(f):
signature = inspect.signature(f)
return {
k: v.default
for k, v in signature.parameters.items()
if v.default is not inspect.Parameter.empty
}
def full_kwargs(f, kwargs):
res = dict(get_default_args(f))
res.update(kwargs)
return res
def mem(func):
cache = dict()
def wrapper(*args, **kwargs):
kwargs = full_kwargs(func, kwargs)
key = list(args)
key.extend(kwargs.values())
key = hash(tuple(key))
if key in cache:
return cache[key]
else:
res = func(*args, **kwargs)
cache[key] = res
return res
return wrapper
结果-睡眠只有3次
但使用from time import sleep
@mem
def count(a, *x, z=10):
sleep(2)
x = list(x)
x.append(z)
x.append(a)
return sum(x)
def main():
print(count(1,2,3,4,5))
print(count(1,2,3,4,5))
print(count(1,2,3,4,5, z=6))
print(count(1,2,3,4,5, z=6))
print(count(1))
print(count(1, z=10))
if __name__ == '__main__':
main()
将是4次,因为:
@lru_cache
将被计算两次(默认设置无效)
答案 14 :(得分:1)
Python 3.8 cached_property
装饰器
cached_property
:https://docs.python.org/dev/library/functools.html#functools.cached_property,但假设派生的版本将被合并到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 作为已定义的广告位之一(因为此类完全不提供 dict 属性)。
答案 15 :(得分:0)
尝试joblib http://pythonhosted.org/joblib/memory.html
from joblib import Memory
memory = Memory(cachedir=cachedir, verbose=0)
@memory.cache
def f(x):
print('Running f(%s)' % x)
return x
答案 16 :(得分:0)
如果您正在使用Django并希望缓存视图,请参阅Nikhil Kumar's answer。
但是如果要缓存任何函数结果,可以使用django-cache-utils。
它重用Django缓存并提供易于使用的cached
装饰器:
from cache_utils.decorators import cached
@cached(60)
def foo(x, y=0):
print 'foo is called'
return x+y
答案 17 :(得分:0)
functools.cache
在Python 3.9(docs)中发布:
from functools import cache
@cache
def factorial(n):
return n * factorial(n-1) if n else 1
在以前的版本中,one of the early answers仍然是有效的解决方案,使用lru_cache
作为没有限制和lru功能的普通缓存。 (docs)
如果maxsize设置为None,则禁用LRU功能并缓存 可以无限地成长。
这是它的更漂亮的版本:
cache = lru_cache(maxsize=None)
@cache
def func(param1):
pass