考虑以下几点:
@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,真正的计算不依赖于可变值
当前回答
@lru_cache不适合默认attrs
我的@mem装饰:
import inspect
from copy import deepcopy
from functools import lru_cache, wraps
from typing import Any, Callable, Dict, Iterable
# helper
def get_all_kwargs_values(f: Callable, kwargs: Dict[str, Any]) -> Iterable[Any]:
default_kwargs = {
k: v.default
for k, v in inspect.signature(f).parameters.items()
if v.default is not inspect.Parameter.empty
}
all_kwargs = deepcopy(default_kwargs)
all_kwargs.update(kwargs)
for key in sorted(all_kwargs.keys()):
yield all_kwargs[key]
# the best decorator
def mem(func: Callable) -> Callable:
cache = dict()
@wraps(func)
def wrapper(*args, **kwargs) -> Any:
all_kwargs_values = get_all_kwargs_values(func, kwargs)
params = (*args, *all_kwargs_values)
_hash = hash(params)
if _hash not in cache:
cache[_hash] = func(*args, **kwargs)
return cache[_hash]
return wrapper
# some logic
def counter(*args) -> int:
print(f'* not_cached:', end='\t')
return sum(args)
@mem
def check_mem(a, *args, z=10) -> int:
return counter(a, *args, z)
@lru_cache
def check_lru(a, *args, z=10) -> int:
return counter(a, *args, z)
def test(func) -> None:
print(f'\nTest {func.__name__}:')
print('*', func(1, 2, 3, 4, 5))
print('*', func(1, 2, 3, 4, 5))
print('*', func(1, 2, 3, 4, 5, z=6))
print('*', func(1, 2, 3, 4, 5, z=6))
print('*', func(1))
print('*', func(1, z=10))
def main():
test(check_mem)
test(check_lru)
if __name__ == '__main__':
main()
输出:
Test check_mem:
* not_cached: * 25
* 25
* not_cached: * 21
* 21
* not_cached: * 11
* 11
Test check_lru:
* not_cached: * 25
* 25
* not_cached: * 21
* 21
* not_cached: * 11
* not_cached: * 11
其他回答
尝试joblib https://joblib.readthedocs.io/en/latest/memory.html
from joblib import Memory
memory = Memory(cachedir=cachedir, verbose=0)
@memory.cache
def f(x):
print('Running f(%s)' % x)
return x
from functools import wraps
def cache(maxsize=128):
cache = {}
def decorator(func):
@wraps(func)
def inner(*args, no_cache=False, **kwargs):
if no_cache:
return func(*args, **kwargs)
key_base = "_".join(str(x) for x in args)
key_end = "_".join(f"{k}:{v}" for k, v in kwargs.items())
key = f"{key_base}-{key_end}"
if key in cache:
return cache[key]
res = func(*args, **kwargs)
if len(cache) > maxsize:
del cache[list(cache.keys())[0]]
cache[key] = res
return res
return inner
return decorator
def async_cache(maxsize=128):
cache = {}
def decorator(func):
@wraps(func)
async def inner(*args, no_cache=False, **kwargs):
if no_cache:
return await func(*args, **kwargs)
key_base = "_".join(str(x) for x in args)
key_end = "_".join(f"{k}:{v}" for k, v in kwargs.items())
key = f"{key_base}-{key_end}"
if key in cache:
return cache[key]
res = await func(*args, **kwargs)
if len(cache) > maxsize:
del cache[list(cache.keys())[0]]
cache[key] = res
return res
return inner
return decorator
示例使用
import asyncio
import aiohttp
# Removes the aiohttp ClientSession instance warning.
class HTTPSession(aiohttp.ClientSession):
""" Abstract class for aiohttp. """
def __init__(self, loop=None) -> None:
super().__init__(loop=loop or asyncio.get_event_loop())
def __del__(self) -> None:
if not self.closed:
self.loop.run_until_complete(self.close())
self.loop.close()
return
session = HTTPSession()
@async_cache()
async def query(url, method="get", res_method="text", *args, **kwargs):
async with getattr(session, method.lower())(url, *args, **kwargs) as res:
return await getattr(res, res_method)()
async def get(url, *args, **kwargs):
return await query(url, "get", *args, **kwargs)
async def post(url, *args, **kwargs):
return await query(url, "post", *args, **kwargs)
async def delete(url, *args, **kwargs):
return await query(url, "delete", *args, **kwargs)
Python 3.8 functools。cached_property装饰
https://docs.python.org/dev/library/functools.html#functools.cached_property
来自Werkzeug的cached_property在:https://stackoverflow.com/a/5295190/895245上提到过,但据说派生版本将合并到3.8中,这是非常棒的。
这个装饰器可以被看作是缓存@property,或者是清洁器@functools。Lru_cache,当你没有任何参数时。
医生说:
@functools.cached_property(func) Transform a method of a class into a property whose value is computed once and then cached as a normal attribute for the life of the instance. Similar to property(), with the addition of caching. Useful for expensive computed properties of instances that are otherwise effectively immutable. Example: 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) New in version 3.8. Note This decorator requires that the dict attribute on each instance be a mutable mapping. This means it will not work with some types, such as metaclasses (since the dict attributes on type instances are read-only proxies for the class namespace), and those that specify slots without including dict as one of the defined slots (as such classes don’t provide a dict attribute at all).
在Python Wiki中还有另一个备忘录装饰器的例子:
http://wiki.python.org/moin/PythonDecoratorLibrary#Memoize
这个例子有点聪明,因为如果参数是可变的,它不会缓存结果。(检查代码,它非常简单和有趣!)
函数缓存简单解决方案
TTL(时间到生命)和max_entries
当修饰函数接受不可哈希类型作为输入(例如dicts)时,不工作 可选参数:TTL(每个条目的生存时间) 可选参数:max_entries(如果缓存参数组合太多,不会使存储混乱) 确保该函数没有重要的副作用
示例使用
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()
# delete the the first expired entry that can be found (lazy deletion)
for key in function.cache:
if function.cache[key]['expiry'] < now:
del function.cache[key]
break
# 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