Recientemente, revisé una base de código existente que contiene muchas clases donde los atributos de instancia reflejan valores almacenados en una base de datos. He refactorizado muchos de estos atributos para que sus búsquedas en la base de datos se difieran, es decir. no debe inicializarse en el constructor sino solo en la primera lectura. Estos atributos no cambian durante la vida útil de la instancia, pero son un verdadero cuello de botella para calcular esa primera vez y solo se accede a ellos realmente para casos especiales. Por lo tanto, también se pueden almacenar en caché después de que se hayan recuperado de la base de datos (esto, por lo tanto, se ajusta a la definición de memorización donde la entrada es simplemente "sin entrada").
Me encuentro escribiendo el siguiente fragmento de código una y otra vez para varios atributos en varias clases:
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
¿Existe un decorador existente para hacer esto en Python que simplemente no conozco? ¿O hay una forma razonablemente sencilla de definir un decorador que haga esto?
Estoy trabajando con Python 2.5, pero las respuestas 2.6 aún pueden ser interesantes si son significativamente diferentes.
Nota
Esta pregunta se hizo antes de que Python incluyera muchos decoradores preparados para esto. Lo he actualizado solo para corregir la terminología.
functools.lru_cache()
.Respuestas:
Para todo tipo de grandes utilidades, estoy usando boltons .
Como parte de esa biblioteca, tiene la propiedad en caché :
fuente
Aquí hay una implementación de ejemplo de un decorador de propiedades perezoso:
Sesión interactiva:
fuente
__get__
@wraps(fn)
continuación@property
para no perder sus cadenas de documentos, etc. (wraps
viene defunctools
)Escribí este para mí ... Para usarlo en verdaderas propiedades perezosas calculadas una sola vez . Me gusta porque evita pegar atributos extra en los objetos, y una vez activado no pierde tiempo comprobando la presencia de atributos, etc .:
Nota: La
lazy_property
clase es un descriptor que no es de datos , lo que significa que es de solo lectura. Agregar un__set__
método evitaría que funcione correctamente.fuente
fget
la forma@property
. Para garantizar la inmutabilidad / idempotencia, debe agregar un__set__()
método que aumenteAttributeError('can\'t set attribute')
(o cualquier excepción / mensaje que le convenga, pero esto es lo queproperty
surge). Desafortunadamente, esto tiene un impacto en el rendimiento de una fracción de microsegundo porque__get__()
se llamará en cada acceso en lugar de extraer el valor de fget de dict en el segundo acceso y en los siguientes. En mi opinión, vale la pena mantener la inmutabilidad / idempotencia, que es clave para mis casos de uso, pero YMMV.He aquí una invocable que toma un argumento opcional de tiempo de espera, en el
__call__
que también podría copiar el__name__
,__doc__
,__module__
del espacio de nombres de func:ex:
fuente
property
es una clase. Un descriptor para ser exactos. Simplemente deriva de él e implementa el comportamiento deseado.fuente
Lo que realmente quieres es el decorador
reify
(fuente vinculada) de Pyramid:fuente
:)
pyramid
dependencia.Hay una mezcla de términos y / o confusión de conceptos tanto en la pregunta como en las respuestas hasta ahora.
La evaluación diferida solo significa que algo se evalúa en tiempo de ejecución en el último momento posible cuando se necesita un valor.
El(*) La función decorada se evalúa solo y cada vez que se necesita el valor de esa propiedad. (consulte el artículo de wikipedia sobre evaluación perezosa)@property
decorador estándar hace precisamente eso.(*) En realidad, una verdadera evaluación perezosa (compárese, por ejemplo, con haskell) es muy difícil de lograr en Python (y da como resultado un código que está lejos de ser idiomático).
La memorización es el término correcto para lo que parece estar buscando el solicitante. Las funciones puras que no dependen de los efectos secundarios para la evaluación del valor de retorno se pueden memorizar de forma segura y, de hecho, hay un decorador en funciones,
@functools.lru_cache
por lo que no es necesario escribir decoradores propios a menos que necesite un comportamiento especializado.fuente
@property
, "lazy" no tiene mucho sentido en ese momento. (También pensé en memoisation como un mapa de entradas a salidas en caché, y puesto que estas propiedades tienen una sola entrada, nada, un mapa que parecía más la complejidad de lo necesario.)Puede hacer esto de forma agradable y sencilla creando una clase a partir de la propiedad nativa de Python:
Podemos usar esta clase de propiedad como propiedad de clase normal (también es compatible con la asignación de elementos como puede ver)
Valor solo calculado la primera vez y después de eso usamos nuestro valor guardado
Salida:
fuente