クラスメンバーを初期化する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
代わりに _@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
はメタクラスを使用して拡張できます。
この回答は、典型的なインスタンス属性/メソッドのみであり、クラス属性/ classmethod
、またはstaticmethod
ではありません。 。
property
と lru_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による。
コードをよりクリーンにする別のアプローチは、目的のロジックを実行するラッパー関数を作成することです。
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
をもう少し複雑にする必要があることに注意してください。
Ring
はlru_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
詳細については、リンクを参照してください。
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'