2010-10-27 17 views
31

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

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

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

+2

IMO ни один из этих ответов не верен. OP хотел получить кешированное свойство _class_, например 'Foo.something_expensive'. Все эти ответы касаются свойств кэшированного _instance_, что означает, что 'something_expensive' будет пересчитан для каждого нового экземпляра, который в большинстве случаев является менее оптимальным. – steve

ответ

26

Обычный способ был бы сделать приписывать 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 
+0

В Python3.2 + есть ли какая-либо мотивация использовать этот подход для' @property + @ functools.lru_cache() '? Способ квази-частного атрибута, похоже, напоминает Java/seters/getters; по моему скромному мнению, только украшение с lru_cache больше pythonic –

+0

(Как в @ Maxime's [ответ] (https://stackoverflow.com/a/19979379/7954504)) –

-2

Самый простой способ сделать это, вероятно, состоял бы в том, чтобы просто написать метод (вместо использования атрибута), который обтекает атрибут (метод getter). При первом вызове этот метод вычисляет, сохраняет и возвращает значение; позже он просто возвращает сохраненное значение.

1

Вы можете попробовать искать в запоминание. Способ, которым он работает, заключается в том, что если вы передадите функции с теми же аргументами, она вернет результат кэширования. Вы можете найти более подробную информацию о implementing it in python here.

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

2
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}) 
36

Раньше я делал это, как предположил гниблер, но я в конце концов устал от маленьких шагов по уборке.

Так что я построил свой собственный дескриптор:

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. 
+7

Замечательно! Вот как это работает: переменные экземпляра [имеют приоритет над дескрипторами без данных] (https://docs.python.org/2/howto/descriptor.html#descriptor-protocol). При первом доступе атрибута атрибут экземпляра отсутствует, но только атрибут класса дескриптора и, следовательно, дескриптор выполняется. Однако при его выполнении дескриптор создает атрибут экземпляра с кешированным значением. Это означает, что при обращении к атрибуту во второй раз возвращается ранее созданный атрибут экземпляра вместо исполняемого дескриптора. –

+1

В пакете ['cached_property' на PyPI] (https://pypi.python.org/pypi/cached-property). Он включает поточно-безопасные и устаревшие версии. (Кроме того, спасибо, @Florian, для объяснения.) – leewz

+2

Yay для эзотерических угловых случаев: вы не можете использовать дескриптор 'cached_property' при использовании' __slots__'. Слоты реализованы с использованием дескрипторов данных, а дескриптор 'cached_property' просто переопределяет созданный дескриптор слота, поэтому вызов' setattr() 'не будет работать, поскольку не существует' __dict__', чтобы установить атрибут и доступен только один дескриптор для этого имени атрибута является 'cached_property' .. Просто поместите это здесь, чтобы помочь другим избежать этой ловушки. –

21
  • 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 

This answer имеет более подробные примеры, а также упоминает Backport для предыдущих версий Python.

  • Python < 3,2

Питон вики имеет cached property decorator (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) 

Или любая реализация упомянутых в других ответов, соответствует вашим потребностям.
Или вышеупомянутый backport.

+3

lru_cache также был отправлен обратно на python 2: https://pypi.python.org/pypi/functools32/3.2.3 – Buttons840

+0

-1 Размер файла lru_cache' равен 128, поэтому функцию свойства можно вызвать дважды. Если вы будете использовать 'lru_cache (None), все экземпляры будут постоянно оставаться в живых. – orlp

+2

@orlp lru_cache имеет размер по умолчанию 128, для 128 различных конфигураций аргументов. Это будет проблемой только в том случае, если вы создаете больше объектов, чем размер вашего кеша, поскольку единственный изменяющийся аргумент здесь - это сам. Если вы генерируете так много объектов, вы действительно не должны использовать неограниченный кеш, поскольку это заставит вас хранить все объекты, которые когда-либо называли свойство в памяти, на неопределенное время, что может быть ужасающей утечкой памяти. Независимо от того, вам, вероятно, было бы лучше с помощью метода кэширования, который хранит кеш в самом объекте, поэтому кеш очищается вместе с ним. – Taywee

Смежные вопросы