Недавно я просмотрел существующую базу кода, содержащую множество классов, в которых атрибуты экземпляра отражают значения, хранящиеся в базе данных. Я отредактировал многие из этих атрибутов, чтобы их поиск в базе данных был отложен, т.е. не инициализироваться в конструкторе, а только при первом чтении. Эти атрибуты не изменяются за время существования экземпляра, но они являются настоящим узким местом для вычисления в первый раз и действительно доступны только в особых случаях. Следовательно, их также можно кэшировать после того, как они были извлечены из базы данных (поэтому это соответствует определению мемоизации, где ввод просто «нет ввода»).
Я ловлю себя на том, что снова и снова набираю следующий фрагмент кода для различных атрибутов в разных классах:
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 включил для этого множество готовых декораторов. Я обновил его только для исправления терминологии.
functools.lru_cache()
.Ответы:
Для самых разных утилит я использую болтоны .
Как часть этой библиотеки у вас есть cachedproperty :
источник
Вот пример реализации ленивого декоратора свойств:
Интерактивная сессия:
источник
__get__
@wraps(fn)
ниже,@property
чтобы не потерять строки документа и т. Д. (wraps
functools
Я написал это для себя ... Чтобы использовать его для истинных одноразовых ленивых свойств. Мне это нравится, потому что он позволяет избежать наложения дополнительных атрибутов на объекты, а после активации не тратит время на проверку наличия атрибутов и т. Д .:
Примечание.
lazy_property
Класс не является дескриптором данных , что означает, что он доступен только для чтения. Добавление__set__
метода может помешать его правильной работе.источник
fget
пути@property
делает. Чтобы гарантировать неизменность / идемпотентность, вам нужно добавить__set__()
метод, который вызываетAttributeError('can\'t set attribute')
(или любое другое исключение / сообщение, которое вам подходит, но это то, чтоproperty
вызывает). К сожалению, это влияет на производительность в доли микросекунды, потому что__get__()
будет вызываться при каждом доступе, а не извлекать значение fget из dict при втором и последующих доступах . На мой взгляд, стоит поддерживать неизменяемость / идемпотентность, что является ключевым для моих вариантов использования, но YMMV.Вот отозваны , который принимает необязательный аргумент тайм - аута, в
__call__
вы также можете скопировать над__name__
,__doc__
,__module__
из пространства имен Func в:например:
источник
property
это класс. Если быть точным, дескриптор . Просто сделайте вывод из этого и реализуйте желаемое поведение.источник
Что вам действительно нужно, так это декоратор
reify
(ссылка на источник!) От Pyramid:источник
:)
pyramid
зависимости.До сих пор наблюдается смешение терминов и / или смешение понятий как в вопросе, так и в ответах.
Ленивая оценка просто означает, что что-то оценивается во время выполнения в последний возможный момент, когда требуется значение.
Стандартный(*) Декорированная функция оценивается только каждый раз, когда вам нужно значение этого свойства. (см. статью в Википедии о ленивой оценке)@property
декоратор делает именно это.(*) На самом деле истинно ленивое вычисление (сравните, например, с haskell) очень сложно достичь в python (и приводит к коду, который далеко не идиоматичен).
Мемоизация - это правильный термин для обозначения того, что, похоже, ищет спрашивающий. Чистые функции , которые не зависят от побочных эффектов для оценки возвращаемого значения могут быть безопасна memoized и есть на самом деле декоратор в functools
@functools.lru_cache
поэтому нет необходимости для написания собственных декораторов , если не нужно специализированное поведение.источник
@property
, "ленивый" не имеет большого смысла на данном этапе. (Я также думал о мемоизации как о карте входов в кэшированные выходы, и поскольку эти свойства имеют только один вход, ничего, карта казалась более сложной, чем необходимо.)Вы можете сделать это красиво и легко, создав класс из собственного свойства Python:
Мы можем использовать этот класс свойств как обычное свойство класса (он также поддерживает назначение элементов, как вы можете видеть)
Значение рассчитывается только в первый раз, после чего мы использовали сохраненное значение.
Вывод:
источник