[python] 단순히 함수 반환 값을 캐시하는 데코레이터가 있습니까?

다음을 고려하세요:

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

가장 최근 호출을 최대 크기까지 저장하는 메모가있는 콜러 블로 함수를 래핑하는 데코레이터입니다. 비용이 많이 들거나 I / O 바운드 함수가 동일한 인수로 주기적으로 호출 될 때 시간을 절약 할 수 있습니다.

피보나치 수 계산을위한 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를 사용하는 경우 호환 가능한 다른 메모 라이브러리 목록은 다음과 같습니다.


답변

범용 메모 화 데코레이터를 요구 하지 않는 것처럼 들립니다 (즉, 다른 인수 값에 대한 반환 값을 캐시하려는 일반적인 경우에는 관심이 없습니다). 즉, 다음이 필요합니다.

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)

캐시 크기에 제한이있는 또 다른 메모 데코레이터는 여기 에서 찾을 수 있습니다 .


답변

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'}


답변

Python 3.8 functools.cached_property데코레이터

https://docs.python.org/dev/library/functools.html#functools.cached_property

cached_propertyWerkzeug의 내용은 https://stackoverflow.com/a/5295190/895245 에서 언급 되었지만 파생 된 버전이 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 속성은 클래스 네임 스페이스에 대한 읽기 전용 프록시 이기 때문에)와 같은 일부 유형 과 정의 된 슬롯 중 하나로 dict 를 포함하지 않고 슬롯 을 지정하는 유형 (클래스와 같은)에서는 작동하지 않습니다. dict 속성을 전혀 제공하지 마십시오 ).


답변

Werkzeug에는 cached_property데코레이터가 있습니다 ( docs , source )


답변

이 간단한 데코레이터 클래스를 코딩하여 함수 응답을 캐시했습니다. 내 프로젝트에 매우 유용합니다.

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


답변

면책 조항 : 저는 kids.cache 의 저자입니다 .

을 확인해야 합니다. python 2 및 python 3에서 작동 kids.cache하는 @cache데코레이터를 제공합니다. 종속성 없음, ~ 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있습니다.

속성에 캐시를 사용하면이라고 게으른 평가 , kids.cache훨씬 더 (가 … 인수, 속성, 메서드의 모든 종류, 심지어 클래스와 기능을 작동) 할 수 있습니다. 고급 사용자의 경우 python 2 및 python 3 (LRU, LFU, TTL, RR 캐시)에 멋진 캐시 저장소를 제공하는 kids.cache지원 cachetools.

중요 참고 :의 기본 캐시 저장소 kids.cache는 표준 dict이며, 이는 계속해서 증가하는 캐싱 저장소로 이어질 수 있으므로 쿼리가 다른 장기 실행 프로그램에는 권장되지 않습니다. 이 사용법을 위해 예를 들어 ( @cache(use=cachetools.LRUCache(maxsize=2))함수 / 속성 / 클래스 / 메소드를 장식하기 위해 …)를 사용하여 다른 캐시 저장소를 플러그인 할 수 있습니다 .