209

次の点を考慮してください。

@property
def name(self):

    if not hasattr(self, '_name'):

        # expensive calculation
        self._name = 1 + 1

    return self._name

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

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

4

19 に答える 19

252

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

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

最大 maxsize の最新の呼び出しを保存する memoizing callable で関数をラップするデコレーター。高価な関数または I/O バウンド関数が同じ引数で定期的に呼び出される場合、時間を節約できます。

フィボナッチ数を計算するための LRU キャッシュの例:

from functools import lru_cache

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

于 2012-03-12T20:28:46.620 に答える
36

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

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)

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

于 2009-05-02T16:42:33.143 に答える
35

Python 3.8functools.cached_propertyデコレーター

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

cached_propertyWerkzeug からはhttps://stackoverflow.com/a/5295190/895245で言及されましたが、派生バージョンと思われるものは 3.8 にマージされます。これは素晴らしいことです。

このデコレータは、 caching @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を含めずにスロットを指定するもの (そのようなクラスなど) など、一部の型では機能しないことを意味します。 dict属性をまったく指定しないでください)。

于 2019-05-12T09:15:51.190 に答える
26
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'}
于 2014-03-21T07:27:09.240 に答える
11

Werkzeug にはcached_propertyデコレータがあります ( docssource )

于 2011-03-14T05:48:16.467 に答える
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))
于 2015-06-07T21:53:33.240 に答える
6

ああ、これの正しい名前を見つける必要がありました:「怠惰なプロパティの評価」。

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

于 2009-05-03T03:25:54.390 に答える
6

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

確認する必要があります。これは、 python 2 および python 3 で動作するデコレータをkids.cache提供します。依存関係はなく、コードは 100 行までです。@cacheたとえば、コードを念頭に置いて使用するのは非常に簡単です。次のように使用できます。

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 に高度なキャッシュ ストアを提供するkids.cacheサポート(LRU、LFU、TTL、RR キャッシュ)。cachetools

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

于 2015-04-27T08:46:35.023 に答える
3

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

例:

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

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

于 2013-05-09T10:00:02.690 に答える
3

Python Wikiにmemoizeデコレータのさらに別の例があります。

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

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

于 2010-01-14T00:40:44.637 に答える
3

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
于 2018-03-03T08:19:55.197 に答える
2

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

  • キャッシュピー; ttl および/またはキャッシュされた関数の呼び出し回数を設定できます。また、暗号化されたファイルベースのキャッシュを使用できます...
  • パーキャッシュ
于 2016-04-26T06:36:31.360 に答える
1

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

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

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

クラスがそのコンテンツの安全なハッシュを返すunique_hash () プロトコルがあればいいのですが。基本的に、気になるタイプについては手動で実装しました。

于 2013-09-12T21:01:43.943 に答える
1

関数キャッシュのシンプルなソリューション

ttl (存続時間) および max_entries を使用

  • 装飾された関数が入力としてハッシュ不可能な型を取る場合 (例: 辞書) は機能しません。
  • オプションのパラメータ: ttl (すべてのエントリの生存時間)
  • オプションのパラメーター: max_entries (キャッシュ引数の組み合わせが多すぎてストレージが乱雑にならない場合)
  • 関数に重大な副作用がないことを確認してください

使用例

import time

@cache(ttl=timedelta(minutes=3), max_entries=300)
def add(a, b):
    time.sleep(2)
    return a + b

@cache()
def substract(a, b):
    time.sleep(2)
    return a - b

a = 5
# function is called with argument combinations the first time -> it takes some time
for i in range(5):
    print(add(a, i))

# function is called with same arguments again? -> will answer from cache
for i in range(5):
    print(add(a, i))

デコレータのコードをコピー

from datetime import datetime, timedelta

def cache(**kwargs):
  def decorator(function):
    # static function variable for cache, lazy initialization
    try: function.cache
    except: function.cache = {}
    def wrapper(*args):
        # if nothing valid in cache, insert something
        if not args in function.cache or datetime.now() > function.cache[args]['expiry']:
            if 'max_entries' in kwargs:
                max_entries = kwargs['max_entries']
                if max_entries != None and len(function.cache) >= max_entries:
                    now = datetime.now()
                    # delete the the first expired entry that can be found (lazy deletion)
                    for key in function.cache:
                        if function.cache[key]['expiry'] < now:
                            del function.cache[key]
                            break
                    # if nothing is expired that is deletable, delete the first
                    if len(function.cache) >= max_entries:
                        del function.cache[next(iter(function.cache))]
            function.cache[args] = {'result': function(*args), 'expiry': datetime.max if 'ttl' not in kwargs else datetime.now() + kwargs['ttl']}

        # answer from cache
        return function.cache[args]['result']
    return wrapper
  return decorator
于 2021-05-16T09:46:06.783 に答える
0
from functools import wraps


def cache(maxsize=128):
    cache = {}

    def decorator(func):
        @wraps(func)
        def inner(*args, no_cache=False, **kwargs):
            if no_cache:
                return func(*args, **kwargs)

            key_base = "_".join(str(x) for x in args)
            key_end = "_".join(f"{k}:{v}" for k, v in kwargs.items())
            key = f"{key_base}-{key_end}"

            if key in cache:
                return cache[key]

            res = func(*args, **kwargs)

            if len(cache) > maxsize:
                del cache[list(cache.keys())[0]]
                cache[key] = res

            return res

        return inner

    return decorator


def async_cache(maxsize=128):
    cache = {}

    def decorator(func):
        @wraps(func)
        async def inner(*args, no_cache=False, **kwargs):
            if no_cache:
                return await func(*args, **kwargs)

            key_base = "_".join(str(x) for x in args)
            key_end = "_".join(f"{k}:{v}" for k, v in kwargs.items())
            key = f"{key_base}-{key_end}"

            if key in cache:
                return cache[key]

            res = await func(*args, **kwargs)

            if len(cache) > maxsize:
                del cache[list(cache.keys())[0]]
                cache[key] = res

            return res

        return inner

    return decorator

使用例

import asyncio
import aiohttp


# Removes the aiohttp ClientSession instance warning.
class HTTPSession(aiohttp.ClientSession):
    """ Abstract class for aiohttp. """
    
    def __init__(self, loop=None) -> None:
        super().__init__(loop=loop or asyncio.get_event_loop())

    def __del__(self) -> None:
        if not self.closed:
            self.loop.run_until_complete(self.close())
            self.loop.close()
 

        return 
       

            

session = HTTPSession()

@async_cache()
async def query(url, method="get", res_method="text", *args, **kwargs):
    async with getattr(session, method.lower())(url, *args, **kwargs) as res:
        return await getattr(res, res_method)()


async def get(url, *args, **kwargs):
    return await query(url, "get", *args, **kwargs)
 

async def post(url, *args, **kwargs):
    return await query(url, "post", *args, **kwargs)

async def delete(url, *args, **kwargs):
    return await query(url, "delete", *args, **kwargs)
于 2022-01-21T00:10:20.443 に答える