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


157

以下を検討してください。

@property
def name(self):

    if not hasattr(self, '_name'):

        # expensive calculation
        self._name = 1 + 1

    return self._name

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

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


そのような機能を持つデコレータが世の中にあるかもしれませんが、あなたはあなたが望むものを完全に指定していません。どのようなキャッシュバックエンドを使用していますか?そして、値はどのようにキーイングされますか?私はあなたのコードから、あなたが本当に求めているのはキャッシュされた読み取り専用プロパティであると仮定しています。
David Berger、

いわゆる「キャッシング」を実行するメモデコレータがあります。それらは通常、(メソッドになるかどうかに関係なく)関数自体に作用し、その結果は引数に依存します(selfなどの変更可能なものではありません!-)。
Alex Martelli、

回答:


206

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に悩まされている場合、互換性のある他のメモ化ライブラリのリストを次に示します。


1

バックポートはここにあります:pypi.python.org/pypi/backports.functools_lru_cache
Frederick Nord、

@gerritは理論的にはハッシュ可能なオブジェクトに対して一般的に機能します。ただし、一部のハッシュ可能なオブジェクトは、それらが同じオブジェクトである場合にのみ等しくなります(明示的な__hash __()関数のないユーザー定義オブジェクトのように)。
ジョナサン

1
@ジョナサンそれは動作しますが、間違っています。ハッシュ可能で変更可能な引数を渡して、関数の最初の呼び出し後にオブジェクトの値を変更すると、2番目の呼び出しは元のオブジェクトではなく、変更されたオブジェクトを返します。それはほぼ間違いなくユーザーが望んでいることではありません。可変引数で機能するためlru_cacheには、キャッシュしている結果のコピーを作成する必要があり、functools.lru_cache実装ではそのようなコピーは作成されません。これを行うと、ラージオブジェクトをキャッシュするために使用するときに、見つけにくいメモリの問題が発生するリスクがあります。
gerrit

@gerritここでフォローアップしていただけますか:stackoverflow.com/questions/44583381/…?私はあなたの例に完全に従わなかった。
ジョナサン

28

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

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)

キャッシュサイズに制限のある別のメモ化デコレータは、ここにあります


すべての回答で言及されているデコレータはメソッドでは機能しません!おそらくそれらはクラスベースであるためです。自分だけが渡されますか?その他は正常に機能しますが、値を関数に格納するのは無作法です。
トビアス

2
argsがハッシュ可能でない場合、問題が発生する可能性があります。
不明

1
@Unknownはい、ここで私が引用した最初のデコレータはハッシュ可能な型に限定されています。ActiveState(キャッシュサイズ制限あり)での引数は、引数を(ハッシュ可能)文字列にピクルしますが、これはもちろん、より高価ですがより一般的です。
Nathan Kitchen、

@vanityクラスベースのデコレータの制限を指摘してくれてありがとう。メソッドで機能するデコレーター関数を表示するように私の回答を修正しました(私は実際にこれをテストしました)。
Nathan Kitchen、

1
@SiminJieデコレータは一度だけ呼び出され、それが返すラップされた関数は、へのすべての異なる呼び出しに使用されるものと同じfibonacciです。その関数は常に同じmemo辞書を使用します。
Nathan Kitchen

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

おかしい!これはどのように作動しますか?私が見た他のデコレータのようには見えません。
PascalVKooten 16

1
このソリューションは、foo(3、b = 5)などのキーワード引数を使用するとTypeErrorを返します
kadee

1
解決策の問題は、メモリの制限がないことです。名前付き引数については、** nargsのように__ call__および__ missing__に追加するだけです
Leonid Mednikov

16

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)

クラスのメソッドを、値が1回計算され、インスタンスの有効期間中は通常の属性としてキャッシュされるプロパティに変換します。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を含めずにスロットを指定するタイプ(そのようなクラスなど)では機能しないことを意味しますdict属性をまったく提供しないでください)。



9

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

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

1
最初@cachedは括弧がありません。それ以外の場合はcached、代わりにオブジェクトを返すだけmyfuncで、呼び出されたmyfunc()場合innerは常に戻り値として返されます
Markus Meskanen

6

免責事項私はkids.cacheの作成者です。

確認する必要kids.cacheがあります@cache。Python2およびPython 3で動作するデコレータが提供されています。依存関係はありません。コードは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は標準の辞書です。これは、異なるクエリで長時間実行されるプログラムには推奨されません。これは、キャッシュストアが増大し続けるためです。この使用法では、たとえば、(@cache(use=cachetools.LRUCache(maxsize=2))関数/プロパティ/クラス/メソッドを装飾するために...)を使用して他のキャッシュストアをプラグインできます。


このモジュールは、Pythonの2〜0.9sに遅いインポート時になるようだ(参照:pastebin.com/raw/aA1ZBE9Zを)。これは、この行github.com/0k/kids.cache/blob/master/src/kids/__init__.py#L3(setuptoolsのエントリポイントを参照)が原因であると思われます。これについて問題を作成しています。
2017年

上記のgithub.com/0k/kids.cache/issues/9の問題は次のとおりです
2017年

これにより、メモリリークが発生します。
Timothy Zhang

@vaabはのインスタンスcを作成し、MyClassそれを使用して検査しobjgraph.show_backrefs([c], max_depth=10)ます。クラスオブジェクトMyClassからへの参照チェーンがありますc。つまり、cリリースされるまでリリースされませんでしたMyClass
ティモシーチャン

@TimothyZhangが招待され、github.com / 0k / kids.cache / issues / 10に懸念事項を追加できます。Stackoverflowは、それについて適切に議論するための適切な場所ではありません。そして、さらなる明確化が必要です。ご意見ありがとうございます。
vaab


4

「Python 3 functools.lru_cacheのC実装。標準ライブラリの10〜30倍のスピードアップを提供します」というfastcacheがあります。

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

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

また、インストールが必要な functoolsと異なり、Anaconda にもインストールされます。


1
functoolsは標準ライブラリの一部であり、投稿したリンクはランダムなgitフォークなどへのリンクです
cz


3

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

例:

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

詳細については、こちらをご覧ください


2

Memoizeの例とともに、次のPythonパッケージを見つけました。

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

1

私はこのようなものを実装し、永続化に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

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

1

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

結果-睡眠で3回のみ

しかし、@lru_cacheこれは4回になります。これは、

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

2回計算されます(デフォルトでの作業は不適切)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.