Инициализация элемента класса 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
Ответы
Ответ 1
Вместо этого вы можете использовать @property
для метакласса:
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
могут быть расширены с помощью метаклассов.
Ответ 2
Другой подход, чтобы очистить код, - написать функцию-обертку, которая выполняет нужную логику:
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
Теперь ваш класс будет выглядеть следующим образом, например:
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
немного сложнее.
Ответ 3
Этот ответ для типичного атрибута экземпляра/метода только, а не для атрибута класса /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.
Кредит: ответ Максим Р.
Ответ 4
Рассмотрим устанавливаемый pip пакет 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'
Ответ 5
Ring
предоставляет lru_cache
-like, но работает с любыми дескрипторами: 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
Пройдите по ссылке, чтобы узнать больше.