次の点を考慮してください。
@property
def name(self):
if not hasattr(self, '_name'):
# expensive calculation
self._name = 1 + 1
return self._name
私は新しいのですが、キャッシュはデコレータに分解できると思います。私はそれのようなものを見つけられませんでした;)
PS実際の計算は可変値に依存しません
次の点を考慮してください。
@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)
最大 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 に行き詰まっている場合は、互換性のある他のメモ化ライブラリのリストを次に示します。
汎用のメモ化デコレータを求めていないようです(つまり、さまざまな引数値の戻り値をキャッシュする一般的なケースには関心がありません)。つまり、これが必要です:
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)
キャッシュ サイズに制限のある別のメモ化デコレータは、こちらにあります。
Python 3.8functools.cached_property
デコレーター
https://docs.python.org/dev/library/functools.html#functools.cached_property
cached_property
Werkzeug からは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属性をまったく指定しないでください)。
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'}
この単純なデコレータ クラスをコーディングして、関数の応答をキャッシュしました。私は自分のプロジェクトに非常に役立つと思います:
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
提供します。依存関係はなく、コードは 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))
関数/プロパティ/クラス/メソッドを装飾するために...)を使用して他のキャッシュストアをプラグインできます
Django Framework を使用している場合は、API の使用のビューまたは応答をキャッシュするプロパティが@cache_page(time)
あり、他のオプションもある場合があります。
例:
@cache_page(60 * 15, cache="special_cache")
def my_view(request):
...
詳細については、こちらをご覧ください。
Python Wikiにmemoizeデコレータのさらに別の例があります。
http://wiki.python.org/moin/PythonDecoratorLibrary#Memoize
この例は、パラメーターが変更可能な場合に結果をキャッシュしないため、少しスマートです。(そのコードをチェックしてください。非常にシンプルで興味深いものです!)
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
私は永続化のために pickle を使用し、短いほぼ確実に一意の ID のために sha1 を使用して、このようなものを実装しました。基本的に、キャッシュは関数のコードと引数のヒストをハッシュしてsha1を取得し、そのsha1を名前に含むファイルを探しました。存在する場合は、それを開いて結果を返しました。そうでない場合は、関数を呼び出して結果を保存します (オプションで、処理に一定の時間がかかった場合にのみ保存します)。
そうは言っても、これを行う既存のモジュールを見つけて、そのモジュールを見つけようとしてここにいることを誓います...私が見つけることができる最も近いものはこれです。 io/blog/2011/11/23/pythondjango-disk-based-caching-decorator.html
私が目にする唯一の問題は、巨大な配列に固有ではない str(arg) をハッシュするため、大きな入力に対してはうまく機能しないことです。
クラスがそのコンテンツの安全なハッシュを返すunique_hash () プロトコルがあればいいのですが。基本的に、気になるタイプについては手動で実装しました。
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
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)