web-dev-qa-db-ja.com

関数の戻り値を単純にキャッシュするデコレータはありますか?

以下を考慮してください。

@property
def name(self):

    if not hasattr(self, '_name'):

        # expensive calculation
        self._name = 1 + 1

    return self._name

私は新しいですが、キャッシュをデコレーターに組み込むことができると思います。私はそれのようなものを見つけられませんでした;)

PS実際の計算は変更可能な値に依存しません

128
Tobias

Python 3.2から始まり、組み込みのデコレータがあります:

@functools.lru_cache(maxsize=100, typed=False)

最新の呼び出しを最大maxsizeまで保存するメモ呼び出し可能オブジェクトで関数をラップするデコレーター。高価なまたは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にこだわっている場合、互換性のある他のメモ化ライブラリのリストを以下に示します。

166
Paolo Moretti

あなたはnot汎用メモデコレータを求めているように聞こえます(つまり、キャッシュしたい一般的なケースには興味がありません異なる引数値の戻り値)。つまり、これが必要です:

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)

キャッシュサイズに制限のある別のメモ化デコレータを見つけることができます here

26
Nathan Kitchen
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'}
21
acmerfight

Werkzeugにはcached_propertyデコレータ( docssource

9
Imran

関数応答をキャッシュするために、この単純なデコレータークラスをコーディングしました。私のプロジェクトにとって非常に便利だと思います。

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))
8
Pablo Besada

免責事項:私は kids.cache の著者です。

_kids.cache_を確認する必要があります。これは、python 2およびpython 3で機能する_@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_デコレータを置くこともできます(同じ結果)。

プロパティでのキャッシュの使用はlazy evaluationと呼ばれ、_kids.cache_はさらに多くのことができます(引数、プロパティ、メソッドのタイプ、クラスでさえ機能します) ...)。上級ユーザーの場合、_kids.cache_は、python 2およびpython 3(LRU、LFU、TTL、RRキャッシュ)に派手なキャッシュストアを提供するcachetoolsをサポートします。

重要な注意:_kids.cache_のデフォルトのキャッシュストアは標準の辞書であり、異なるクエリを使用する長時間実行プログラムには推奨されませんキャッシングストアの成長につながります。この使用法では、たとえば(@cache(use=cachetools.LRUCache(maxsize=2))を使用して他のキャッシュストアをプラグインして、関数/プロパティ/クラス/メソッドを装飾できます...)

6
vaab

ああ、ちょうどこれのための正しい名前を見つける必要がありました: " Lazy property evaluation "。

私もこれをたくさんやっています。多分私はいつか私のレシピでそのレシピを使うでしょう。

5
Ken Arnold

Python 3.8 cached_propertyデコレータ

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

cached_property Werkzeugから: 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属性はクラスネームスペースの読み取り専用プロキシであるため)定義されたスロットの1つとしてdictを含めずにslotsを指定するもの(そのようなクラスはdict属性をまったく提供しないため)。

Django Framework)を使用している場合、@cache_page(time)を使用してAPIのビューまたは応答をキャッシュするプロパティがあり、他のオプションもあります。

例:

@cache_page(60 * 15, cache="special_cache")
def my_view(request):
    ...

詳細は こちら をご覧ください。

3
Nikhil Kumar

memoizeデコレーターの別の例がPython Wiki:

http://wiki.python.org/moin/PythonDecoratorLibrary#Memoize

この例は、パラメーターが変更可能な場合に結果をキャッシュしないため、少しスマートです。 (そのコードを確認してください、それは非常にシンプルで面白いです!)

3

fastcache があります。これは、「Python 3 functools.lru_cacheのC実装です。標準ライブラリを10〜30倍高速化します。」

選択した回答 と同じ、インポートが異なるだけです:

from fastcache import lru_cache
@lru_cache(maxsize=128, typed=False)
def f(a, b):
    pass

また、 Anaconda にインストールされています。functoolsとは異なり インストールする必要があります です。

2
Romi Kuntsman

Memoize Example とともに、次のpythonパッケージが見つかりました:

  • cachepy ;キャッシュされた関数のttlおよび/または呼び出し回数を設定できます。また、暗号化されたファイルベースのキャッシュを使用できます...
  • percache
2
bubble

@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

およびテスト用のコード:

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 __== '__main__':
    main()

結果-スリープ状態で3回のみ

しかし、@lru_cacheこれは次の理由で4回になります。

print(count(1))
print(count(1, z=10))

2回計算されます(デフォルトでの動作不良)

1
Sublimer

永続化にpickleを使用し、ほぼ確実に一意の短いIDにsha1を使用して、このようなものを実装しました。基本的に、キャッシュは関数のコードと引数の履歴をハッシュしてsha1を取得し、名前にそのsha1が含まれるファイルを探しました。存在する場合、それを開き、結果を返しました。そうでない場合は、関数を呼び出して結果を保存します(オプションで、処理に一定の時間がかかった場合のみ保存します)。

とはいえ、これを行った既存のモジュールを見つけたと断言し、そのモジュールを見つけようとしていることをここで見つけます...私が見つけることができる最も近いものは、右に見えるこれです: http:// chase -seibert.github.io/blog/2011/11/23/pythondjango-disk-based-caching-decorator.html

私がそれで見る唯一の問題は、巨大な配列に固有ではないstr(arg)をハッシュするため、大きな入力ではうまく機能しないことです。

クラスがその内容の安全なハッシュを返すunique_hash()プロトコルがあればいいでしょう。基本的に、私が気にしたタイプのためにそれを手動で実装しました。

1
Ben

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
0
Dror Hilman

Djangoを使用していて、ビューをキャッシュしたい場合は、 Nikhil Kumarの答え を参照してください。


ただし、任意の関数結果をキャッシュする場合は、 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
0
Greg Dubicki