web-dev-qa-db-ja.com

Pythonクラスメンバーの遅延初期化

クラスメンバーを初期化するpython方法は何ですか?ただし、アクセスした場合にのみアクセスします。以下のコードを試してみましたが、機能していますが、それよりも簡単な方法はありますか?

class MyClass(object):

    _MY_DATA = None

    @staticmethod
    def _retrieve_my_data():
        my_data = ...  # costly database call
        return my_data

    @classmethod
    def get_my_data(cls):
        if cls._MY_DATA is None:
            cls._MY_DATA = MyClass._retrieve_my_data()
        return cls._MY_DATA
20
user1919510

代わりに _@property_ on メタクラス を使用できます。

_class MyMetaClass(type):
    @property
    def my_data(cls):
        if getattr(cls, '_MY_DATA', None) is None:
            my_data = ...  # costly database call
            cls._MY_DATA = my_data
        return cls._MY_DATA


class MyClass(metaclass=MyMetaClass):
    # ...
_

これにより、_my_data_がクラスの属性になるため、_MyClass.my_data_にアクセスしようとするまで、コストのかかるデータベース呼び出しが延期されます。データベース呼び出しの結果は、それを_MyClass._MY_DATA_に格納することによってキャッシュされ、呼び出しはクラスに対して1回だけ行われます

Python 2の場合、class MyClass(object):を使用し、クラス定義本体に ___metaclass__ = MyMetaClass_属性 を追加してメタクラスをアタッチします。

デモ:

_>>> class MyMetaClass(type):
...     @property
...     def my_data(cls):
...         if getattr(cls, '_MY_DATA', None) is None:
...             print("costly database call executing")
...             my_data = 'bar'
...             cls._MY_DATA = my_data
...         return cls._MY_DATA
... 
>>> class MyClass(metaclass=MyMetaClass):
...     pass
... 
>>> MyClass.my_data
costly database call executing
'bar'
>>> MyClass.my_data
'bar'
_

これは、propertyのようなデータ記述子がオブジェクトの親タイプで検索されるために機能します。 typeであるクラスの場合、typeはメタクラスを使用して拡張できます。

24
Martijn Pieters

この回答は、典型的なインスタンス属性/メソッドのみであり、クラス属性/ classmethod、またはstaticmethodではありません。 。

propertylru_cache デコレータの両方を使用するのはどうですか?後者はメモ化します。

from functools import lru_cache

class MyClass:

    @property
    @lru_cache()
    def my_lazy_attr(self):
        print('Initializing and caching attribute, once per class instance.')
        return 7**7**8

これにはPython≥3.2が必要であることに注意してください。

クレジット: answer MaximeRによる。

5
Acumenus

コードをよりクリーンにする別のアプローチは、目的のロジックを実行するラッパー関数を作成することです。

def memoize(f):
    def wrapped(*args, **kwargs):
        if hasattr(wrapped, '_cached_val'):
            return wrapped._cached_val
        result = f(*args, **kwargs)
        wrapped._cached_val = result
        return result
    return wrapped

次のように使用できます。

@memoize
def expensive_function():
    print "Computing expensive function..."
    import time
    time.sleep(1)
    return 400

print expensive_function()
print expensive_function()
print expensive_function()

どの出力:

Computing expensive function...
400
400
400

これで、classmethodは次のようになります。例:

class MyClass(object):
        @classmethod
        @memoize
        def retrieve_data(cls):
            print "Computing data"
            import time
            time.sleep(1) #costly DB call
            my_data = 40
            return my_data

print MyClass.retrieve_data()
print MyClass.retrieve_data()
print MyClass.retrieve_data()

出力:

Computing data
40
40
40

これにより、関数への引数のセットに対して1つの値のみがキャッシュされるため、入力値に応じて異なる値を計算する場合は、memoizeをもう少し複雑にする必要があることに注意してください。

5
Claudiu

Ringlru_cacheのようなインターフェースを提供しますが、あらゆる種類の記述子をサポートします: https://ring-cache.readthedocs.io/en/latest/quickstart.html#method- classmethod-staticmethod

class Page(object):
    (...)

    @ring.lru()
    @classmethod
    def class_content(cls):
        return cls.base_content

    @ring.lru()
    @staticmethod
    def example_dot_com():
        return requests.get('http://example.com').content

詳細については、リンクを参照してください。

0
youknowone

Python 3.5+で利用可能なpip-installable Dickens パッケージを検討してください。関連するdescriptorsパッケージがあります。 cachedpropertyおよびcachedclasspropertyデコレータ、その使用法を以下の例に示します。期待どおりに機能しているようです。

from descriptors import cachedproperty, classproperty, cachedclassproperty

class MyClass:
    FOO = 'A'

    def __init__(self):
        self.bar = 'B'

    @cachedproperty
    def my_cached_instance_attr(self):
        print('Initializing and caching attribute, once per class instance.')
        return self.bar * 2

    @cachedclassproperty
    def my_cached_class_attr(cls):
        print('Initializing and caching attribute, once per class.')
        return cls.FOO * 3

    @classproperty
    def my_class_property(cls):
        print('Calculating attribute without caching.')
        return cls.FOO + 'C'
0
Acumenus