Подтвердить что ты не робот

Кэширование атрибутов класса в Python

Я пишу класс в python, и у меня есть атрибут, который займет относительно много времени для вычисления, поэтому Я хочу сделать это только раз. Кроме того, он не понадобится каждому экземпляру класса, поэтому я не хочу делать это по умолчанию в __init__.

Я новичок в Python, но не для программирования. Я могу придумать способ сделать это довольно легко, но я снова и снова обнаружил, что способ "что-то делать" Pythonic часто намного проще, чем то, что я придумываю, используя свой опыт на других языках.

Есть ли "правильный" способ сделать это в Python?

4b9b3361

Ответ 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). При первом вызове этот метод вычисляет, сохраняет и возвращает значение; позже он просто возвращает сохраненное значение.