最近、データベースに保存された値をインスタンス属性が反映する多くのクラスを含む既存のコードベースを調べました。これらの属性の多くをリファクタリングして、データベースのルックアップを遅延させました。コンストラクタで初期化されるのではなく、最初の読み取り時にのみ初期化されます。これらの属性は、インスタンスの存続期間にわたって変化することはありませんが、それらを初めて計算する実際のボトルネックであり、特別な場合にのみ実際にアクセスされます。したがって、データベースから取得した後にキャッシュすることもできます(したがって、これはmemoisationの定義に適合します(入力は単に「入力なし」)。
さまざまなクラスのさまざまな属性について、次のコードスニペットを何度も入力しています。
class testA(object):
def __init__(self):
self._a = None
self._b = None
@property
def a(self):
if self._a is None:
# Calculate the attribute now
self._a = 7
return self._a
@property
def b(self):
#etc
既にPythonに気づいていない既存のデコレータがありますか?または、これを行うデコレータを定義する合理的な簡単な方法はありますか?
私はPython 2.5で作業していますが、2.6の答えが大きく異なる場合は、まだ興味深いかもしれません。
この質問はPythonの前に行われました。これには多くの既製のデコレーターが含まれていました。用語を修正するためにのみ更新しました。
あらゆる種類の素晴らしいユーティリティに対して、私は boltons を使用しています。
そのライブラリの一部として cachedproperty があります:
from boltons.cacheutils import cachedproperty
class Foo(object):
def __init__(self):
self.value = 4
@cachedproperty
def cached_prop(self):
self.value += 1
return self.value
f = Foo()
print(f.value) # initial value
print(f.cached_prop) # cached property is calculated
f.value = 1
print(f.cached_prop) # same value for the cached property - it isn't calculated again
print(f.value) # the backing value is different (it's essentially unrelated value)
遅延プロパティデコレータの実装例は次のとおりです。
import functools
def lazyprop(fn):
attr_name = '_lazy_' + fn.__name__
@property
@functools.wraps(fn)
def _lazyprop(self):
if not hasattr(self, attr_name):
setattr(self, attr_name, fn(self))
return getattr(self, attr_name)
return _lazyprop
class Test(object):
@lazyprop
def a(self):
print 'generating "a"'
return range(5)
インタラクティブセッション:
>>> t = Test()
>>> t.__dict__
{}
>>> t.a
generating "a"
[0, 1, 2, 3, 4]
>>> t.__dict__
{'_lazy_a': [0, 1, 2, 3, 4]}
>>> t.a
[0, 1, 2, 3, 4]
私はこれを自分用に書きました... trueone-time計算された遅延プロパティに使用されます。オブジェクトに余分な属性を貼り付けないようにし、一度アクティブ化すると属性の存在などを確認する時間を無駄にしないため、私はそれが好きです:
import functools
class lazy_property(object):
'''
meant to be used for lazy evaluation of an object attribute.
property should represent non-mutable data, as it replaces itself.
'''
def __init__(self, fget):
self.fget = fget
# copy the getter function's docstring and other attributes
functools.update_wrapper(self, fget)
def __get__(self, obj, cls):
if obj is None:
return self
value = self.fget(obj)
setattr(obj, self.fget.__name__, value)
return value
class Test(object):
@lazy_property
def results(self):
calcs = 1 # Do a lot of calculation here
return calcs
注:lazy_property
クラスは 非データ記述子 です。つまり、読み取り専用です。 __set__
メソッドを追加すると、正しく機能しなくなります。
オプションのタイムアウト引数をとる呼び出し可能オブジェクトは、__call__
で、funcの名前空間から__name__
、__doc__
、__module__
をコピーすることもできます。
import time
class Lazyproperty(object):
def __init__(self, timeout=None):
self.timeout = timeout
self._cache = {}
def __call__(self, func):
self.func = func
return self
def __get__(self, obj, objcls):
if obj not in self._cache or \
(self.timeout and time.time() - self._cache[key][1] > self.timeout):
self._cache[obj] = (self.func(obj), time.time())
return self._cache[obj]
例:
class Foo(object):
@Lazyproperty(10)
def bar(self):
print('calculating')
return 'bar'
>>> x = Foo()
>>> print(x.bar)
calculating
bar
>>> print(x.bar)
bar
...(waiting 10 seconds)...
>>> print(x.bar)
calculating
bar
property
はクラスです。 記述子 正確に。単純にそれから派生し、目的の動作を実装します。
class lazyproperty(property):
....
class testA(object):
....
a = lazyproperty('_a')
b = lazyproperty('_b')
これまで、問題と答えの両方で用語の混同や概念の混乱があります。
遅延評価とは、実行時に値が必要な最後の瞬間に何かが評価されることを意味します。 標準 (*)装飾された関数は、そのプロパティの値が必要になるたびにのみ評価されます。 (遅延評価に関するウィキペディアの記事を参照してください)@property
デコレータはまさにそれを行います。
(*)実際には、python(および慣用句とはほど遠いコードが生成されます)で、真の遅延評価(例:haskellと比較)を達成するのは非常に困難です。
メモ化は、質問者が探しているものの正しい用語です。戻り値の評価のために副作用に依存しない純粋な関数は安全にメモでき、実際には functools@functools.lru_cache
したがって、特別な動作が必要な場合を除き、独自のデコレータを記述する必要はありません。
Pythonネイティブプロパティ:
class cached_property(property):
def __init__(self, func, name=None, doc=None):
self.__= name or func.__name__
self.__module__ = func.__module__
self.__doc__ = doc or func.__doc__
self.func = func
def __set__(self, obj, value):
obj.__dict__[self.__name__] = value
def __get__(self, obj, type=None):
if obj is None:
return self
value = obj.__dict__.get(self.__name__, None)
if value is None:
value = self.func(obj)
obj.__dict__[self.__name__] = value
return value
このプロパティクラスを通常のクラスプロパティのように使用できます(ご覧のようにアイテムの割り当てもサポートしています)
class SampleClass():
@cached_property
def cached_property(self):
print('I am calculating value')
return 'My calculated value'
c = SampleClass()
print(c.cached_property)
print(c.cached_property)
c.cached_property = 2
print(c.cached_property)
print(c.cached_property)
最初に計算された値のみ、その後は保存された値を使用しました
出力:
I am calculating value
My calculated value
My calculated value
2
2