考虑以下几点:

@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

其他回答

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

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

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)

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

创建自己的装饰器并使用它

from django.core.cache import cache
import functools

def cache_returned_values(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        key = "choose a unique key here"
        results = cache.get(key)
        if not results:
            results = func(*args, **kwargs)
            cache.set(key, results)
        return results

    return wrapper

现在看函数

@cache_returned_values
def get_some_values(args):
  return x

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

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

如果你正在使用Django并且想要缓存视图,请参阅Nikhil Kumar的回答。

但是如果你想缓存任何函数的结果,你可以使用django-cache-utils。

它重用了Django缓存,并提供了易于使用的缓存装饰器:

from cache_utils.decorators import cached

@cached(60)
def foo(x, y=0):
    print 'foo is called'
    return x+y