Кэширование атрибутов класса в Python
Я пишу класс в python, и у меня есть атрибут, который займет относительно много времени для вычисления, поэтому Я хочу сделать это только раз. Кроме того, он не понадобится каждому экземпляру класса, поэтому я не хочу делать это по умолчанию в __init__
.
Я новичок в Python, но не для программирования. Я могу придумать способ сделать это довольно легко, но я снова и снова обнаружил, что способ "что-то делать" Pythonic часто намного проще, чем то, что я придумываю, используя свой опыт на других языках.
Есть ли "правильный" способ сделать это в Python?
Ответы
Ответ 1
Python ≥ 3.2
Вы должны использовать декораторы @property
и @functools.lru_cache
:
import functools
class MyClass:
@property
@functools.lru_cache()
def foo(self):
print("long calculation here")
return 21 * 2
Этот ответ содержит более подробные примеры, а также упоминает бэкпорт для предыдущих версий Python.
Python <3.2
Вики Python имеет декоратор кэшированных свойств (лицензированный MIT), который можно использовать так:
import random
# the class containing the property must be a new-style class
class MyClass(object):
# create property whose value is cached for ten minutes
@cached_property(ttl=600)
def randint(self):
# will only be evaluated every 10 min. at maximum.
return random.randint(0, 100)
Или любая реализация, упомянутая в других ответах, которая соответствует вашим потребностям.
Или вышеупомянутый бэкпорт.
Ответ 2
Я использовал это, как предположил гниблер, но я в конце концов устал от небольших шагов по уборке.
Итак, я построил свой собственный дескриптор:
class cached_property(object):
"""
Descriptor (non-data) for building an attribute on-demand on first use.
"""
def __init__(self, factory):
"""
<factory> is called such: factory(instance) to build the attribute.
"""
self._attr_name = factory.__name__
self._factory = factory
def __get__(self, instance, owner):
# Build the attribute.
attr = self._factory(instance)
# Cache the value; hide ourselves.
setattr(instance, self._attr_name, attr)
return attr
Вот как вы его используете:
class Spam(object):
@cached_property
def eggs(self):
print 'long calculation here'
return 6*2
s = Spam()
s.eggs # Calculates the value.
s.eggs # Uses cached value.
Ответ 3
Обычным способом было бы сделать атрибут property и сохранить значение при первом вычислении
import time
class Foo(object):
def __init__(self):
self._bar = None
@property
def bar(self):
if self._bar is None:
print "starting long calculation"
time.sleep(5)
self._bar = 2*2
print "finished long caclulation"
return self._bar
foo=Foo()
print "Accessing foo.bar"
print foo.bar
print "Accessing foo.bar"
print foo.bar
Ответ 4
class MemoizeTest:
_cache = {}
def __init__(self, a):
if a in MemoizeTest._cache:
self.a = MemoizeTest._cache[a]
else:
self.a = a**5000
MemoizeTest._cache.update({a:self.a})
Ответ 5
Вы могли бы попытаться заглянуть в воспоминания. Способ, которым он работает, заключается в том, что если вы передадите функции с теми же аргументами, она вернет результат кэширования. Более подробную информацию о можно найти здесь в python.
Кроме того, в зависимости от того, как настроен ваш код (вы говорите, что он не нужен всем экземплярам), вы можете попытаться использовать какой-то мухи или ленивую загрузку.
Ответ 6
Python 3.8 включает в себя functools.cached_property
декоратор.
Преобразуйте метод класса в свойство, значение которого вычисляется один раз, а затем кэшируется как нормальный атрибут для жизни экземпляр. Аналогично property()
, с добавлением кеширования. полезным для дорогих вычисляемых свойств экземпляров, которые в противном случае эффективно неизменным.
Этот пример прямо из документов:
from functools import cached_property
class DataSet:
def __init__(self, sequence_of_numbers):
self._data = sequence_of_numbers
@cached_property
def stdev(self):
return statistics.stdev(self._data)
@cached_property
def variance(self):
return statistics.variance(self._data)
Ограничением является то, что объект с кешируемым свойством должен иметь атрибут __dict__
, который является изменяемым отображением, исключая классы с помощью __slots__
, если __dict__
не определен в __slots__
.
Ответ 7
Пакет dickens
(не мой) предлагает декораторы cachedproperty
, classproperty
и cachedclassproperty
.
Чтобы кэшировать свойство класса:
from descriptors import cachedclassproperty
class MyClass:
@cachedclassproperty
def approx_pi(cls):
return 22 / 7
Ответ 8
С Python 2, но не с Python 3, вот что я делаю. Это настолько эффективно, насколько это возможно:
class X:
@property
def foo(self):
r = 33
self.foo = r
return r
Объяснение: По сути, я просто перегружаю метод свойства вычисленным значением. Таким образом, после первого доступа к свойству (для этого экземпляра) foo
перестает быть свойством и становится атрибутом экземпляра. Преимущество этого подхода состоит в том, что попадание в кеш является как можно более дешевым, поскольку в качестве кеша используется self.__dict__
, и нет никаких дополнительных затрат экземпляра, если свойство не используется.
Этот подход не работает с Python 3.
Ответ 9
Самый простой способ сделать это - это просто написать метод (вместо использования атрибута), который обертывает атрибут (метод getter). При первом вызове этот метод вычисляет, сохраняет и возвращает значение; позже он просто возвращает сохраненное значение.