web-dev-qa-db-ja.com

Python memoising / deferred lookup property decorator

最近、データベースに保存された値をインスタンス属性が反映する多くのクラスを含む既存のコードベースを調べました。これらの属性の多くをリファクタリングして、データベースのルックアップを遅延させました。コンストラクタで初期化されるのではなく、最初の読み取り時にのみ初期化されます。これらの属性は、インスタンスの存続期間にわたって変化することはありませんが、それらを初めて計算する実際のボトルネックであり、特別な場合にのみ実際にアクセスされます。したがって、データベースから取得した後にキャッシュすることもできます(したがって、これは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の前に行われました。これには多くの既製のデコレーターが含まれていました。用語を修正するためにのみ更新しました。

103
detly

あらゆる種類の素晴らしいユーティリティに対して、私は 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)
9
guyarad

遅延プロパティデコレータの実装例は次のとおりです。

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]
123
Mike Boers

私はこれを自分用に書きました... 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__メソッドを追加すると、正しく機能しなくなります。

107
Cyclone

オプションのタイムアウト引数をとる呼び出し可能オブジェクトは、__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
4
gnr

propertyはクラスです。 記述子 正確に。単純にそれから派生し、目的の動作を実装します。

class lazyproperty(property):
   ....

class testA(object):
   ....
  a = lazyproperty('_a')
  b = lazyproperty('_b')

これまで、問題と答えの両方で用語の混同や概念の混乱があります。

遅延評価とは、実行時に値が必要な最後の瞬間に何かが評価されることを意味します。 標準 @propertyデコレータはまさにそれを行います。(*)装飾された関数は、そのプロパティの値が必要になるたびにのみ評価されます。 (遅延評価に関するウィキペディアの記事を参照してください)

(*)実際には、python(および慣用句とはほど遠いコードが生成されます)で、真の遅延評価(例:haskellと比較)を達成するのは非常に困難です。

メモ化は、質問者が探しているものの正しい用語です。戻り値の評価のために副作用に依存しない純粋な関数は安全にメモでき、実際には functools@functools.lru_cacheしたがって、特別な動作が必要な場合を除き、独自のデコレータを記述する必要はありません。

0
Jason Herbburn

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
0
itmard