Ленивая инициализация члена класса 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
4 ответов
вы могли бы использовать @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
вызов только после для класса.
для 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
может быть расширен с помощью метаклассов.
другой подход к очистке кода-написать функцию-оболочку, которая выполняет желаемую логику:
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
обратите внимание, что это будет кэшировать только одно значение для любого набора аргументов функции, поэтому, если вы хотите вычислить разные значения в зависимости от ввода значения, вы должны будете сделать memoize
немного сложнее.
этот ответ для типичного только атрибут/метод экземпляра, Не для атрибута класса/classmethod
или staticmethod
.
как насчет использования как property
и lru_cache
оформителей? Последние memoizes.
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.
кредит: ответ от Maxime Р.
рассмотрим pip-installable Dickens
пакета, который доступен для Python 3.5+. У него есть 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'