考虑以下几点:

@property
def name(self):

    if not hasattr(self, '_name'):

        # expensive calculation
        self._name = 1 + 1

    return self._name

我是新来的,但我认为缓存可以分解成一个装饰器。只是我没有找到一个这样的;)

PS,真正的计算不依赖于可变值


当前回答

我实现了类似的东西,使用pickle进行持久化,使用sha1进行简短的几乎唯一的id。基本上,缓存对函数代码和参数的历史进行哈希,以获得sha1,然后查找名称为sha1的文件。如果它存在,则打开它并返回结果;如果没有,则调用该函数并保存结果(如果需要一定时间来处理,则可以选择只保存结果)。

也就是说,我发誓我找到了一个现有的模块,它做到了这一点,并发现自己在这里试图找到该模块……我能找到的最接近的是这个,看起来差不多:http://chase-seibert.github.io/blog/2011/11/23/pythondjango-disk-based-caching-decorator.html

我看到的唯一问题是,它不能很好地用于大输入,因为它散列str(arg),这不是唯一的大型数组。

如果有一个unique_hash()协议,让一个类返回其内容的安全散列,那就太好了。我基本上是手动实现我所关心的类型。

其他回答

尝试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

听起来好像您不是在要求一个通用的记忆化装饰器(也就是说,您对想要缓存不同参数值的返回值的一般情况不感兴趣)。也就是说,你想要这样:

x = obj.name  # expensive
y = obj.name  # cheap

而一个通用的记忆装饰器会给你这样的:

x = obj.name()  # expensive
y = obj.name()  # cheap

我认为方法调用语法是更好的风格,因为它暗示了昂贵计算的可能性,而属性语法暗示了快速查找。

[更新:我之前链接并引用的基于类的记忆化装饰器不适用于方法。我用decorator函数替换了它。如果你愿意使用通用的记忆装饰器,这里有一个简单的:

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)

可以在这里找到另一个对缓存大小有限制的内存装饰器。

@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

我编写了这个简单的装饰器类来缓存函数响应。我发现它对我的项目非常有用:

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))

啊,只需要给这个找到一个正确的名字:“懒惰的属性评估”。

我也经常这样做;也许有一天我会在我的代码中使用这个配方。