Python декоратор / отложенный поиск свойства декоратор - PullRequest
102 голосов
/ 10 июня 2010

Недавно я просмотрел существующую кодовую базу, содержащую много классов, где атрибуты экземпляра отражают значения, хранящиеся в базе данных.Я реорганизовал многие из этих атрибутов, чтобы их поиск в базе данных был отложен, т.е.не инициализироваться в конструкторе, а только при первом чтении.Эти атрибуты не меняются в течение всего времени существования экземпляра, но они являются реальным узким местом для вычисления этого первого раза и доступны только для особых случаев.Следовательно, они также могут быть кэшированы после того, как они были извлечены из базы данных (это соответствует определению памятка , где ввод просто «без ввода»).следующий фрагмент кода снова и снова для различных атрибутов в различных классах:

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 ]

123 голосов
/ 10 июня 2010

Вот пример реализации отложенного декоратора свойств:

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]
107 голосов
/ 27 июля 2011

Я написал это для себя ... Чтобы использовать для истинных одноразовых вычисленных ленивых свойств.Мне это нравится, потому что он предотвращает налипание дополнительных атрибутов на объекты и после активации не тратит время на проверку наличия атрибутов и т. Д.дескриптор данных , что означает, что он доступен только для чтения.Добавление метода __set__ помешает его правильной работе.

9 голосов
/ 29 июня 2017

Для всех видов замечательных утилит я использую болты .

В составе этой библиотеки у вас есть 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)
4 голосов
/ 03 января 2012

Вот вызываемый объект, который принимает необязательный аргумент времени ожидания, в __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]

ex:

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
3 голосов
/ 10 июня 2010

property это класс. A дескриптор , если быть точным. Просто извлеките из него и реализуйте желаемое поведение.

class lazyproperty(property):
   ....

class testA(object):
   ....
  a = lazyproperty('_a')
  b = lazyproperty('_b')
2 голосов
/ 04 декабря 2016

То, что вы действительно хотите, это reify (источник связан!) декоратор из Pyramid:

Использовать в качестве декоратора метода класса. Он работает почти так же, как декоратор 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
0 голосов
/ 30 июля 2017

Вы можете сделать это легко и просто, создав класс из собственного свойства 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
0 голосов
/ 24 февраля 2016

Пока что существует перепутывание терминов и / или путаница понятий как в вопросе, так и в ответах.

Ленивая оценка просто означает, что что-то оценивается во время выполнения в последний возможный момент, когда значениенеобходимо. Стандартный @property декоратор делает именно это. (*) Декорированная функция оценивается только и каждый раз, когда вам нужно значение этого свойства.(см. статью в Википедии о ленивой оценке)

(*) На самом деле истинную ленивую оценку (сравните, например, haskell) очень трудно достичь в python (и приводит к коду, который далек от идиоматического).

Запоминание - это правильный термин для того, что, похоже, ищет спрашивающий.Чистые функции, которые не зависят от побочных эффектов для оценки возвращаемого значения, могут быть безопасно запомнены, и на самом деле в functools @functools.lru_cache имеется декоратор, поэтому нет необходимости писать собственные декораторы, если вам не требуется специальное поведение.

...