Питон memoising/отложенного просмотра оформитель собственность
недавно я прошел через существующую базу кода, содержащую много классов, где атрибуты экземпляра отражают значения, хранящиеся в базе данных. Я рефакторинговал много этих атрибутов, чтобы их поиск в базе данных был отложен, т. е. не инициализируется в конструкторе, а только при первом чтении. Эти атрибуты не изменяются в течение всего времени существования экземпляра, но они являются реальным узким местом для вычисления этого первого раза и действительно доступны только для особых случаев. Следовательно, они также могут быть кэшируется после их извлечения из базы данных (поэтому это соответствует определению memoisation где вход просто "без входа").
Я снова и снова набираю следующий фрагмент кода для различных атрибутов в разных классах:
class testA(object):
def __init__(self):
self._a = None
self._b = None
@property
def a(self):
if self._a is None:
# Calculate the attribute now
self._a = 7
return self._a
@property
def b(self):
#etc
есть ли существующий декоратор, чтобы сделать это уже в Python, о котором я просто не знаю? Или, есть ли достаточно простой способ определить декоратора, который делает это?
Я работаю под Python 2.5, но 2.6 ответы все еще могут быть интересны, если они значительно отличаются.
Примечание
этот вопрос был задан до того, как Python включил много готовых декораторов для этого. Я обновил его только для того, чтобы исправить терминологию.
8 ответов:
для всех видов больших утилит я использую Болтоны.
как часть этой библиотеки у вас есть cachedproperty:
from boltons.cacheutils import cachedproperty class Foo(object): def __init__(self): self.value = 4 @cachedproperty def cached_prop(self): self.value += 1 return self.value f = Foo() print(f.value) # initial value print(f.cached_prop) # cached property is calculated f.value = 1 print(f.cached_prop) # same value for the cached property - it isn't calculated again print(f.value) # the backing value is different (it's essentially unrelated value)
вот пример реализации ленивого декоратора свойств:
import functools def lazyprop(fn): attr_name = '_lazy_' + fn.__name__ @property @functools.wraps(fn) def _lazyprop(self): if not hasattr(self, attr_name): setattr(self, attr_name, fn(self)) return getattr(self, attr_name) return _lazyprop class Test(object): @lazyprop def a(self): print 'generating "a"' return range(5)
интерактивные сессии:
>>> t = Test() >>> t.__dict__ {} >>> t.a generating "a" [0, 1, 2, 3, 4] >>> t.__dict__ {'_lazy_a': [0, 1, 2, 3, 4]} >>> t.a [0, 1, 2, 3, 4]
Я написал это для себя... Для использования в true один раз из расчета ленивые свойства. Мне это нравится, потому что он избегает наклеивания дополнительных атрибутов на объекты, и после активации не тратит время на проверку наличия атрибутов и т. д.:
import functools class lazy_property(object): ''' meant to be used for lazy evaluation of an object attribute. property should represent non-mutable data, as it replaces itself. ''' def __init__(self, fget): self.fget = fget # copy the getter function's docstring and other attributes functools.update_wrapper(self, fget) def __get__(self, obj, cls): if obj is None: return self value = self.fget(obj) setattr(obj, self.fget.__name__, value) return value class Test(object): @lazy_property def results(self): calcs = 1 # Do a lot of calculation here return calcs
Примечание:
lazy_property
класс для данных дескриптора, что означает, что он доступен только для чтения. Добавление__set__
метод будет препятствовать его правильной работе.
вот вызываемый, который принимает необязательный аргумент тайм-аута, в
__call__
вы также можете скопировать__name__
,__doc__
,__module__
из пространства имен func:import time class Lazyproperty(object): def __init__(self, timeout=None): self.timeout = timeout self._cache = {} def __call__(self, func): self.func = func return self def __get__(self, obj, objcls): if obj not in self._cache or \ (self.timeout and time.time() - self._cache[key][1] > self.timeout): self._cache[obj] = (self.func(obj), time.time()) return self._cache[obj]
пример:
class Foo(object): @Lazyproperty(10) def bar(self): print('calculating') return 'bar' >>> x = Foo() >>> print(x.bar) calculating bar >>> print(x.bar) bar ...(waiting 10 seconds)... >>> print(x.bar) calculating bar
property
- это класс. А дескриптор чтобы быть точным. Просто выведите из него и реализуйте желаемое поведение.class lazyproperty(property): .... class testA(object): .... a = lazyproperty('_a') b = lazyproperty('_b')
что ты действительно нужен
reify
(источник связан!) декоратор из пирамиды:использовать в качестве декоратора метода класса. Он работает почти так же, как в Python
@property
декоратор, но он помещает результат метода, который он украшает, в экземпляр dict после первого вызова, эффективно заменяя функцию, которую он украшает, переменной экземпляра. Это, на языке Python, дескриптор без данных. Ниже приведен пример и его использование:>>> from pyramid.decorator import reify >>> class Foo(object): ... @reify ... def jammy(self): ... print('jammy called') ... return 1 >>> f = Foo() >>> v = f.jammy jammy called >>> print(v) 1 >>> f.jammy 1 >>> # jammy func not called the second time; it replaced itself with 1 >>> # Note: reassignment is possible >>> f.jammy = 2 >>> f.jammy 2
до сих пор существует смешение терминов и/или путаница понятий как в вопросе, так и в ответах.
ленивости просто означает, что что-то вычисляется во время выполнения, в последний момент, когда требуется значение.
стандартный(*) функция decorated оценивается только и каждый раз, когда вам нужно значение этого свойства. (см. статью Википедии о ленивости)@property
декоратор делает именно это.(*)самом деле истинный ленивый оценка (сравните, например, haskell) очень трудно достичь в python (и приводит к коду, который далек от идиоматического).
Memoization-это правильный термин для того, что искатель, кажется, ищет. Чистые функции, которые не зависят от побочных эффектов для оценки возвращаемого значения, можно безопасно запомнить, и на самом деле есть декоратор в functools
@functools.lru_cache
поэтому нет необходимости писать собственные декораторы, если вам не нужно специализированное поведение.
вы можете сделать это красиво и легко, создав класс из собственного свойства Python:
class cached_property(property): def __init__(self, func, name=None, doc=None): self.__name__ = name or func.__name__ self.__module__ = func.__module__ self.__doc__ = doc or func.__doc__ self.func = func def __set__(self, obj, value): obj.__dict__[self.__name__] = value def __get__(self, obj, type=None): if obj is None: return self value = obj.__dict__.get(self.__name__, None) if value is None: value = self.func(obj) obj.__dict__[self.__name__] = value return value
мы можем использовать этот класс свойств как обычное свойство класса ( это также поддержка назначения элементов, как вы можете видеть)
class SampleClass(): @cached_property def cached_property(self): print('I am calculating value') return 'My calculated value' c = SampleClass() print(c.cached_property) print(c.cached_property) c.cached_property = 2 print(c.cached_property) print(c.cached_property)
значение вычисляется только в первый раз, и после этого мы использовали наш сохраненное значение
выход:
I am calculating value My calculated value My calculated value 2 2