Хранение вычисленных значений в объекте

Недавно я писал кучу кода вроде этого:

class A: def __init__(self, x): self.x = x self._y = None def y(self): if self._y is None: self._y = big_scary_function(self.x) return self._y def z(self, i): return nice_easy_function(self.y(), i) 

В данном классе у меня может быть несколько вещей, работающих таким образом, и у меня могут быть другие вещи, которые используют сохраненные предварительно рассчитанные значения. Это лучший способ сделать что-то или вы порекомендуете что-то другое?

  • Храните кеш в файле functools.lru_cache в Python> = 3.2
  • Использует ли Python статические строки?
  • python сбрасываемый экземпляр метод memoization decorator
  • Python ленивый оценщик
  • Обработчик заметок
  • библиотека memoization для python 2.7
  • Обратите внимание, что я не предварительно вычисляю здесь, потому что вы можете использовать экземпляр A без использования y .

    Я написал образец кода в Python, но мне было бы интересно получить ответы на другие языки, если это необходимо. И наоборот, я хотел бы услышать от Pythonistas о том, считают ли они, что этот код является Pythonic или нет.

  • Как реализовать двоичное дерево поиска в Python?
  • Python: как вызвать метод экземпляра из метода класса того же класса
  • Является ли я .__ dict __. Update (** kwargs) хорошим или плохим стилем?
  • Python - лучший метод вызова суперкласса?
  • Есть ли смысл определять класс внутри другого класса в Python?
  • Храните кеш в файле functools.lru_cache в Python> = 3.2
  • 3 Solutions collect form web for “Хранение вычисленных значений в объекте”

    Первое: это очень распространенный шаблон в Python (в Django IIRC есть cached_property дескриптора cached_property ).

    Это говорит о наличии по меньшей мере двух потенциальных проблем.

    Первый из них является общим для всех реализаций кэшированных свойств, и тот факт, что обычно не ожидает доступа к атрибуту для запуска некоторых тяжелых вычислений. Является ли это действительно вопросом, зависит от контекста (и почти религиозных мнений читателя …)

    Вторая проблема, более конкретная для вашего примера, – это традиционная проблема недействительности кэша / согласованности состояния: здесь у вас функция y как функция x – или, по крайней мере, это то, что можно было бы ожидать, но переименование x не будет обновлять y соответственно. Это можно легко решить в этом случае, сделав x свойством тоже и недействительным _y на сеттере, но тогда у вас будет еще более неожиданное тяжелое вычисление.

    В этом случае (и в зависимости от контекста и стоимости вычислений) я бы, вероятно, сохранил memoization (с признанием недействительности), но предоставил более явный getter, чтобы яснить, что у нас могут быть некоторые вычисления.

    Изменить: я неправильно читаю ваш код и представляю себе декоратор свойств на y – который показывает, насколько распространен этот шаблон;). Но мои замечания по-прежнему имеют смысл специально, когда «самопровозглашенная pythonista» отправляет ответ в пользу вычисленного атрибута.

    Изменить: если вы хотите получить более или менее общее свойство кэширования с недействительностью кеша, вот возможная реализация (может потребоваться больше тестирования и т. Д.):

     class cached_property(object): """ Descriptor that converts a method with a single self argument into a property cached on the instance. It also has a hook to allow for another property setter to invalidated the cache, cf the `Square` class below for an example. """ def __init__(self, func): self.func = func self.__doc__ = getattr(func, '__doc__') self.name = self.encode_name(func.__name__) def __get__(self, instance, type=None): if instance is None: return self if self.name not in instance.__dict__: instance.__dict__[self.name] = self.func(instance) return instance.__dict__[self.name] def __set__(self, instance, value): raise AttributeError("attribute is read-only") @classmethod def encode_name(cls, name): return "_p_cached_{}".format(name) @classmethod def clear_cached(cls, instance, *names): for name in names: cached = cls.encode_name(name) if cached in instance.__dict__: del instance.__dict__[cached] @classmethod def invalidate(cls, *names): def _invalidate(setter): def _setter(instance, value): cls.clear_cached(instance, *names) return setter(instance, value) _setter.__name__ = setter.__name__ _setter.__doc__ = getattr(setter, '__doc__') return _setter return _invalidate class Square(object): def __init__(self, size): self._size = size @cached_property def area(self): return self.size * self.size @property def size(self): return self._size @size.setter @cached_property.invalidate("area") def size(self, size): self._size = size 

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

    Как самопровозглашенная Pythonista, я бы предпочел использовать декоратор property в этой ситуации:

     class A: def __init__(self, x): self.x = x @property def y(self): if not hasattr(self, '_y'): self._y = big_scary_function(self.x) return self._y def z(self, i): return nice_easy_function(self.y, i) 

    Здесь self._y также ленив. property позволяет вам ссылаться на self.x и self.y на self.x и self.y же основу. То есть, работая с экземпляром класса, вы рассматриваете как x и y как атрибуты, хотя y записывается как метод.

    Я также использовал not hasattr(self, '_y') вместо self._y is None , что позволяет мне пропустить объявление self.y = None в __init__ . Вы можете, конечно, использовать свой метод здесь и по-прежнему идти с декоратором property .

    Мой подход к pythonista для EAFP описывается следующим фрагментом.

    Мои классы наследуют _reset_attributes из WithAttributes и используют его для WithAttributes недействительных страшных значений.

     class WithAttributes: def _reset_attributes(self, attributes): assert isinstance(attributes,list) for attribute in attributes: try: delattr(self, '_' + attribute) except: pass class Square(WithAttributes): def __init__(self, size): self._size = size @property def area(self): try: return self._area except AttributeError: self._area = self.size * self.size return self._area @property def size(self): return self._size @size.setter def size(self, size): self._size = size self._reset_attributes('area') 
    Python - лучший язык программирования в мире.