クラスメソッド@cachedproperty
のデコレータを作成しようとしています。メソッドが最初に呼び出されたときに、メソッドがその戻り値で置き換えられるように動作させる必要があります。また、@property
のように動作させて、明示的に呼び出す必要がないようにしたいです。基本的に、それは@property
を除いて区別できないはずです値を一度計算してから保存するだけなので、より高速です。これは、__init__
だと思います。だから、私はこれをやりたいと思っています。
最初に、fget
のproperty
メソッドをオーバーライドしようとしましたが、これは読み取り専用です。
次に、最初に呼び出す必要があるが値をキャッシュするデコレータを実装しようと考えました。これは、決して呼び出す必要のないプロパティタイプのデコレータの最終目標ではありませんが、これは最初に取り組む方が簡単な問題だと思いました。言い換えれば、これは少し単純な問題に対しては機能しない解決策です。
私は試した:
def cachedproperty(func):
""" Used on methods to convert them to methods that replace themselves
with their return value once they are called. """
def cache(*args):
self = args[0] # Reference to the class who owns the method
funcname = inspect.stack()[0][3] # Name of the function, so that it can be overridden.
setattr(self, funcname, func()) # Replace the function with its value
return func() # Return the result of the function
return cache
しかし、これはうまくいかないようです。私はこれをテストしました:
>>> class Test:
... @cachedproperty
... def test(self):
... print "Execute"
... return "Return"
...
>>> Test.test
<unbound method Test.cache>
>>> Test.test()
しかし、クラスがそれ自体をメソッドに渡さなかったというエラーが表示されます。
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method cache() must be called with Test instance as first argument (got nothing instead)
この時点で、私と深いPythonメソッドについての限られた知識は非常に混乱しており、コードがどこで間違っているのか、どのように修正するのかわかりません(私はこれまで書いたことはありません)前のデコレータ)
(@property
のように)最初にクラスメソッドを呼び出した結果を返し、以降のすべてのクエリでキャッシュされた値に置き換えるデコレーターを作成するにはどうすればよいですか?
この質問があまり混乱しないことを願っています。できるだけ説明しました。
まず、Test
をインスタンス化する必要があります
_test = Test()
_
2番目に、inspect
は必要ありません。これは、_func.__name__
_からプロパティ名を取得できるためです。3番目に、property(cache)
を返してpythonすべての魔法を実行します。
_def cachedproperty(func):
" Used on methods to convert them to methods that replace themselves\
with their return value once they are called. "
def cache(*args):
self = args[0] # Reference to the class who owns the method
funcname = func.__name__
ret_value = func(self)
setattr(self, funcname, ret_value) # Replace the function with its value
return ret_value # Return the result of the function
return property(cache)
class Test:
@cachedproperty
def test(self):
print "Execute"
return "Return"
>>> test = Test()
>>> test.test
Execute
'Return'
>>> test.test
'Return'
>>>
_
「」
別の解決策を気にしないのであれば、私はお勧めします lru_cache
例えば
from functools import lru_cache
class Test:
@property
@lru_cache(maxsize=None)
def calc(self):
print("Calculating")
return 1
期待される出力
In [2]: t = Test()
In [3]: t.calc
Calculating
Out[3]: 1
In [4]: t.calc
Out[4]: 1
これはまさに記述子の種類なので、カスタム記述子を使用したほうがよいと思います。そのようです:
class CachedProperty:
def __init__(self, name, get_the_value):
self.name = name
self.get_the_value = get_the_value
def __get__(self, obj, typ):
name = self.name
while True:
try:
return getattr(obj, name)
except AttributeError:
get_the_value = self.get_the_value
try:
# get_the_value can be a string which is the name of an obj method
value = getattr(obj, get_the_value)()
except AttributeError:
# or it can be another external function
value = get_the_value()
setattr(obj, name, value)
continue
break
class Mine:
cached_property = CachedProperty("_cached_property ", get_cached_property_value)
# OR:
class Mine:
cached_property = CachedProperty("_cached_property", "get_cached_property_value")
def get_cached_property_value(self):
return "the_value"
編集:ところで、実際にはカスタム記述子も必要ありません。プロパティ関数内の値をキャッシュするだけで済みます。例えば。:
@property
def test(self):
while True:
try:
return self._test
except AttributeError:
self._test = get_initial_value()
これですべてです。
しかし、多くの人はこれをproperty
の乱用であり、予期しない使用方法であると考えています。そして通常、予期しないことは、それを別のより明確な方法で行う必要があることを意味します。カスタムCachedProperty
記述子は非常に明示的であるため、より多くのコードが必要ですが、そのため、property
アプローチよりも優先します。
次のようなものを使用できます。
def cached(timeout=None):
def decorator(func):
def wrapper(self, *args, **kwargs):
value = None
key = '_'.join([type(self).__name__, str(self.id) if hasattr(self, 'id') else '', func.__name__])
if settings.CACHING_ENABLED:
value = cache.get(key)
if value is None:
value = func(self, *args, **kwargs)
if settings.CACHING_ENABLED:
# if timeout=None Django cache reads a global value from settings
cache.set(key, value, timeout=timeout)
return value
return wrapper
return decorator
キャッシュディクショナリに追加すると、エンティティをキャッシュしていて、プロパティがエンティティごとに異なる値を返す可能性がある場合に備えて、class_id_function
の規則に基づいてキーを生成します。
また、ベンチマークを行うときに一時的にオフにしたい場合に備えて、設定キーCACHING_ENABLED
もチェックします。
ただし、標準のproperty
デコレータはカプセル化されないため、関数のように呼び出す必要があります。または、次のように使用できます(プロパティに限定する理由)。
@cached
@property
def total_sales(self):
# Some calculations here...
pass
また、遅延外部キー関係の結果をキャッシュしている場合、データによっては、選択クエリを実行して一度にすべてをフェッチするよりも、単に集計関数を実行した方が速い場合があることに注意する必要があります。結果セットのすべてのレコードのキャッシュにアクセスします。したがって、フレームワークにDjango-debug-toolbar
などのツールを使用して、シナリオで最もパフォーマンスが高いものを比較します。
Djangoのこのデコレータのバージョンは、あなたが説明したことを正確に実行し、シンプルなので、私のコメントに加えて、ここにコピーします。
class cached_property(object):
"""
Decorator that converts a method with a single self argument into a
property cached on the instance.
Optional ``name`` argument allows you to make cached properties of other
methods. (e.g. url = cached_property(get_absolute_url, name='url') )
"""
def __init__(self, func, name=None):
self.func = func
self.__doc__ = getattr(func, '__doc__')
self.name = name or func.__name__
def __get__(self, instance, type=None):
if instance is None:
return self
res = instance.__dict__[self.name] = self.func(instance)
return res
( ソース )。
ご覧のように、それはfunc.nameを使用して関数の名前を決定し(inspect.stackをいじる必要はありません)、メソッドをinstance.__dict__
。したがって、後続の「呼び出し」は単なる属性ルックアップであり、キャッシュなどは必要ありません。