Recentemente, examinei uma base de código existente contendo muitas classes em que os atributos de instância refletem os valores armazenados em um banco de dados. Refatorei muitos desses atributos para que suas pesquisas de banco de dados fossem adiadas, ou seja, não ser inicializado no construtor, mas apenas na primeira leitura. Esses atributos não mudam durante o tempo de vida da instância, mas são um verdadeiro gargalo para calcular aquela primeira vez e só são realmente acessados para casos especiais. Portanto, eles também podem ser armazenados em cache depois de serem recuperados do banco de dados (isso, portanto, se encaixa na definição de memoisation, onde a entrada é simplesmente "sem entrada").
Eu me pego digitando o seguinte snippet de código repetidamente para vários atributos em várias classes:
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
Já existe um decorador para fazer isso em Python que simplesmente não conheço? Ou existe uma maneira razoavelmente simples de definir um decorador que faça isso?
Estou trabalhando em Python 2.5, mas as respostas 2.6 ainda podem ser interessantes se forem significativamente diferentes.
Nota
Essa pergunta foi feita antes que o Python incluísse muitos decoradores prontos para isso. Eu atualizei apenas para corrigir a terminologia.
functools.lru_cache()
.Respostas:
Para todos os tipos de grandes utilitários, estou usando boltons .
Como parte dessa biblioteca, você armazenou propriedade em cache :
fonte
Aqui está um exemplo de implementação de um decorador de propriedade preguiçoso:
Sessão interativa:
fonte
__get__
@wraps(fn)
abaixo@property
para não perder suas strings de doc etc. (wraps
vem defunctools
)Eu escrevi este para mim mesmo ... Para ser usado para propriedades preguiçosas verdadeiras calculadas de uma só vez . Gosto porque evita colar atributos extras nos objetos e, uma vez ativado, não perde tempo verificando a presença de atributos, etc .:
Observação: a
lazy_property
classe é um descritor sem dados , o que significa que é somente leitura. Adicionar um__set__
método impede que ele funcione corretamente.fonte
fget
forma como o@property
faz. Para garantir a imutabilidade / idempotência, você precisa adicionar um__set__()
método que levantaAttributeError('can\'t set attribute')
(ou qualquer exceção / mensagem adequada a você, mas é isso queproperty
aumenta). Infelizmente, isso vem com um impacto de desempenho de uma fração de microssegundo, porque__get__()
será chamado em cada acesso, em vez de extrair o valor de fget do dict no segundo acesso e subseqüentes. Vale a pena na minha opinião manter a imutabilidade / idempotência, que é fundamental para meus casos de uso, mas YMMV.Aqui está uma função que recebe um argumento de tempo limite opcional, na
__call__
você também pode copiar o__name__
,__doc__
,__module__
de namespace de func:ex:
fonte
property
é uma classe. Um descritor para ser exato. Basta derivar dele e implementar o comportamento desejado.fonte
O que você realmente quer é o decorador
reify
(vinculado à fonte!) Da Pyramid:fonte
:)
pyramid
dependência.Até o momento, há uma mistura de termos e / ou confusão de conceitos em questão e nas respostas.
A avaliação lenta significa apenas que algo é avaliado em tempo de execução no último momento possível quando um valor é necessário.
O padrão(*) A função decorada é avaliada apenas e toda vez que você precisa do valor daquela propriedade. (veja o artigo da wikipedia sobre avaliação preguiçosa)@property
decorador faz exatamente isso.(*) Na verdade, uma avaliação verdadeiramente preguiçosa (compare, por exemplo, haskell) é muito difícil de ser obtida em python (e resulta em um código que está longe de ser idiomático).
Memoização é o termo correto para o que quem pergunta parece estar procurando. Funções puras que não dependem de efeitos colaterais para avaliação de valor de retorno podem ser memoized com segurança e há na verdade um decorador em functools,
@functools.lru_cache
portanto, não há necessidade de escrever seus próprios decoradores, a menos que você precise de um comportamento especializado.fonte
@property
, "preguiçoso" não faz muito sentido nesse ponto. (Eu também pensei no memoisation como um mapa de entradas para saídas em cache e, como essas propriedades têm apenas uma entrada, nada, um mapa parecia mais complexo do que o necessário.)Você pode fazer isso de maneira fácil e agradável criando uma classe a partir da propriedade nativa do Python:
Podemos usar esta classe de propriedade como uma propriedade de classe regular (também é compatível com a atribuição de item, como você pode ver)
Valor calculado apenas na primeira vez e depois disso usamos nosso valor salvo
Resultado:
fonte