¿Cómo puedo hacer dos decoradores en Python que hagan lo siguiente?
@makebold
@makeitalic
def say():
return "Hello"
... que debería volver:
"<b><i>Hello</i></b>"
No estoy tratando de hacerlo de HTML
esta manera en una aplicación real, solo estoy tratando de entender cómo funcionan los decoradores y el encadenamiento de decoradores.
__name__
y, hablando del paquete decorador, la firma de la función).*args
y**kwargs
debe agregarse en la respuesta. La función decorada puede tener argumentos, y se perderán si no se especifica.*args
,**kwargs
. Una manera fácil de resolver estos 3 problemas a la vez es usardecopatch
como se explica aquí . También puede usardecorator
como ya mencionó Marius Gedminas, para resolver los puntos 2 y 3.Si no le gustan las explicaciones largas, vea la respuesta de Paolo Bergantino .
Conceptos básicos del decorador
Las funciones de Python son objetos.
Para comprender los decoradores, primero debe comprender que las funciones son objetos en Python. Esto tiene importantes consecuencias. Veamos por qué con un simple ejemplo:
Mantén esto en mente. Volveremos a él en breve.
¡Otra propiedad interesante de las funciones de Python es que se pueden definir dentro de otra función!
Referencias de funciones
Vale, ¿sigues aquí? Ahora la parte divertida ...
Has visto que las funciones son objetos. Por lo tanto, las funciones:
Eso significa que una función puede
return
otra función .¡Hay más!
Si puede
return
una función, puede pasar una como parámetro:Bueno, solo tienes todo lo necesario para entender a los decoradores. Verá, los decoradores son "envoltorios", lo que significa que le permiten ejecutar código antes y después de la función que decoran sin modificar la función misma.
Decoradores artesanales
Cómo lo harías manualmente:
Ahora, probablemente quieras que cada vez que llames
a_stand_alone_function
,a_stand_alone_function_decorated
se llame en su lugar. Eso es fácil, simplemente sobrescribea_stand_alone_function
con la función devuelta pormy_shiny_new_decorator
:Decoradores desmitificados
El ejemplo anterior, usando la sintaxis del decorador:
Sí, eso es todo, es así de simple.
@decorator
es solo un atajo para:Los decoradores son solo una variante pitónica del patrón de diseño del decorador . Hay varios patrones de diseño clásicos integrados en Python para facilitar el desarrollo (como iteradores).
Por supuesto, puedes acumular decoradores:
Usando la sintaxis del decorador de Python:
El orden en que configuró los decoradores IMPORTA:
Ahora: para responder la pregunta ...
Como conclusión, puede ver fácilmente cómo responder la pregunta:
Ahora puedes irte feliz o quemar tu cerebro un poco más y ver los usos avanzados de los decoradores.
Llevando decoradores al siguiente nivel
Pasar argumentos a la función decorada
Métodos de decoración
Una cosa ingeniosa sobre Python es que los métodos y funciones son realmente los mismos. La única diferencia es que los métodos esperan que su primer argumento sea una referencia al objeto actual (
self
).¡Eso significa que puedes construir un decorador para métodos de la misma manera! Solo recuerda tener
self
en cuenta:Si está haciendo un decorador de uso general, uno que aplicará a cualquier función o método, sin importar sus argumentos, simplemente use
*args, **kwargs
:Pasar argumentos al decorador
Genial, ¿qué dirías sobre pasar argumentos al decorador en sí?
Esto puede torcerse un poco, ya que un decorador debe aceptar una función como argumento. Por lo tanto, no puede pasar los argumentos de la función decorada directamente al decorador.
Antes de apresurarnos a la solución, escribamos un pequeño recordatorio:
Es exactamente lo mismo. "
my_decorator
" se llama. Entonces, cuando le@my_decorator
estás diciendo a Python que llame a la función 'etiquetada por la variable "my_decorator
"'.¡Esto es importante! La etiqueta que le dé puede apuntar directamente al decorador, o no .
Vamos a ponernos malvados. ☺
No es de extrañar aquí.
Hagamos EXACTAMENTE lo mismo, pero omita todas las molestas variables intermedias:
Hagámoslo aún más corto :
Oye, ¿viste eso? ¡Utilizamos una llamada de función con la
@
sintaxis " "! :-)Entonces, volvamos a los decoradores con argumentos. Si podemos usar funciones para generar el decorador sobre la marcha, podemos pasar argumentos a esa función, ¿verdad?
Aquí está: un decorador con argumentos. Los argumentos se pueden establecer como variables:
Como puede ver, puede pasar argumentos al decorador como cualquier función que use este truco. Incluso puedes usarlo
*args, **kwargs
si lo deseas. Pero recuerde que los decoradores solo se llaman una vez . Justo cuando Python importa el script. No puede establecer dinámicamente los argumentos después. Cuando "importa x", la función ya está decorada , por lo que no puede cambiar nada.Practiquemos: decorar un decorador
De acuerdo, como bonificación, te daré un fragmento para que cualquier decorador acepte genéricamente cualquier argumento. Después de todo, para aceptar argumentos, creamos nuestro decorador usando otra función.
Envolvimos el decorador.
¿Algo más que vimos recientemente que la función envuelta?
Oh si, decoradores!
Divirtámonos y escribamos un decorador para los decoradores:
Se puede usar de la siguiente manera:
Lo sé, la última vez que tuviste este sentimiento, fue después de escuchar a un chico decir: "antes de entender la recursividad, primero debes entender la recursividad". Pero ahora, ¿no te sientes bien por dominar esto?
Mejores prácticas: decoradores
El
functools
módulo se introdujo en Python 2.5. Incluye la funciónfunctools.wraps()
, que copia el nombre, el módulo y la cadena de documentos de la función decorada en su envoltorio.(Dato curioso: ¡
functools.wraps()
es un decorador! ☺)¿Cómo pueden ser útiles los decoradores?
Ahora la gran pregunta: ¿para qué puedo usar decoradores?
Parece genial y poderoso, pero un ejemplo práctico sería genial. Bueno, hay 1000 posibilidades. Los usos clásicos están extendiendo el comportamiento de una función desde una biblioteca externa (no puede modificarla), o para la depuración (no desea modificarla porque es temporal).
Puede usarlos para extender varias funciones de una manera SECA, así:
Por supuesto, lo bueno de los decoradores es que puedes usarlos de inmediato en casi cualquier cosa sin tener que volver a escribirlos. SECO, dije:
Propio Python proporciona varios decoradores:
property
,staticmethod
, etc.Esto realmente es un gran parque infantil.
fuente
__closure__
atributo) para extraer la función original sin decorar. Un ejemplo de uso se documenta en esta respuesta que cubre cómo es posible inyectar una función decoradora en un nivel inferior en circunstancias limitadas.@decorator
sintaxis de Python probablemente se usa con mayor frecuencia para reemplazar una función con un cierre de envoltura (como se describe en la respuesta). Pero también puede reemplazar la función con otra cosa. La orden internaproperty
,classmethod
ystaticmethod
decoradores reemplazan la función con un descriptor, por ejemplo. Un decorador también puede hacer algo con una función, como guardar una referencia a él en un registro de algún tipo, y luego devolverlo, sin modificaciones, sin ningún envoltorio.Alternativamente, puede escribir una función de fábrica que devuelva un decorador que envuelva el valor de retorno de la función decorada en una etiqueta que se pasa a la función de fábrica. Por ejemplo:
Esto te permite escribir:
o
Personalmente, habría escrito el decorador de manera algo diferente:
que produciría:
No olvide la construcción para la cual la sintaxis del decorador es una abreviatura:
fuente
def wrap_in_tag(*kwargs)
entonces@wrap_in_tag('b','i')
Parece que las otras personas ya te han dicho cómo resolver el problema. Espero que esto te ayude a entender qué son los decoradores.
Los decoradores son solo azúcar sintáctico.
Esta
se expande a
fuente
@decorator()
(en lugar de@decorator
) es azúcar sintáctico parafunc = decorator()(func)
. Esto también es una práctica común cuando necesitas generar decoradores "sobre la marcha"Y, por supuesto, también puede devolver lambdas desde una función decoradora:
fuente
makebold = lambda f : lambda "<b>" + f() + "</b>"
makebold = lambda f: lambda: "<b>" + f() + "</b>"
makebold = lambda f: lambda *a, **k: "<b>" + f(*a, **k) + "</b>"
functools.wraps
para no descartar la cadena de documentos / firma / nombre desay
@wraps
otro sitio en esta página no me va a ayudar cuando imprimohelp(say)
y obtengo "Ayuda en función <lambda>` en lugar de "Ayuda en función decir" .Los decoradores de Python agregan funcionalidad adicional a otra función
Un decorador en cursiva podría ser como
Tenga en cuenta que una función se define dentro de una función. Lo que básicamente hace es reemplazar una función con la recién definida. Por ejemplo, tengo esta clase
Ahora diga, quiero que ambas funciones impriman "---" después y antes de que estén listas. Podría agregar una impresión "---" antes y después de cada declaración de impresión. Pero como no me gusta repetirme, haré un decorador
Entonces ahora puedo cambiar mi clase a
Para obtener más información sobre decoradores, consulte http://www.ibm.com/developerworks/linux/library/l-cpdecor.html
fuente
self
argumento es necesario porque elnewFunction()
definido enaddDashes()
fue diseñado específicamente para ser un decorador de métodos, no un decorador de funciones generales. Elself
argumento representa la instancia de clase y se pasa a los métodos de clase, ya sea que lo usen o no; consulte la sección titulada Métodos de decoración en la respuesta de @ e-satis.functools.wraps
Usted podría hacer dos decoradores independientes que hacen lo que usted quiere, como se ilustra a continuación directamente. Tenga en cuenta el uso de
*args, **kwargs
en la declaración de lawrapped()
función que admite la función decorada que tiene múltiples argumentos (que no es realmente necesario para el ejemplosay()
función de , pero se incluye por generalidad).Por razones similares, el
functools.wraps
decorador se usa para cambiar los meta atributos de la función envuelta para que sean los del decorado. Esto hace que los mensajes de error y la documentación de la función incrustada (func.__doc__
) sean los de la función decorada en lugar dewrapped()
la de 's.Refinamientos
Como puede ver, hay muchos códigos duplicados en estos dos decoradores. Dada esta similitud, sería mejor para usted hacer una genérica que fuera en realidad una fábrica de decoradores, en otras palabras, una función de decorador que crea otros decoradores. De esa manera, habría menos repetición de código y permitiría seguir el principio DRY .
Para que el código sea más legible, puede asignar un nombre más descriptivo a los decoradores generados de fábrica:
o incluso combinarlos así:
Eficiencia
Si bien los ejemplos anteriores hacen todo el trabajo, el código generado implica una buena cantidad de sobrecarga en forma de llamadas a funciones extrañas cuando se aplican múltiples decoradores a la vez. Esto puede no importar, dependiendo del uso exacto (que podría estar vinculado a E / S, por ejemplo).
Si la velocidad de la función decorada es importante, la sobrecarga se puede mantener en una sola llamada de función adicional escribiendo una función de fábrica de decorador ligeramente diferente que implementa la adición de todas las etiquetas a la vez, para que pueda generar código que evite las llamadas de funciones adicionales incurridas mediante el uso de decoradores separados para cada etiqueta.
Esto requiere más código en el decorador en sí, pero esto solo se ejecuta cuando se aplica a las definiciones de funciones, no más tarde cuando se llaman a ellos mismos. Esto también se aplica al crear nombres más legibles mediante el uso de
lambda
funciones como se ilustra anteriormente. Muestra:fuente
Otra forma de hacer lo mismo:
O, más flexiblemente:
fuente
functools.update_wrapper
para mantenersayhi.__name__ == "sayhi"
Desea la siguiente función, cuando se llama:
Regresar:
Solución simple
Para hacer esto simplemente, haga decoradores que devuelvan lambdas (funciones anónimas) que se cierren sobre la función (cierres) y llámela:
Ahora úselos como desee:
y ahora:
Problemas con la solución simple.
Pero parece que casi hemos perdido la función original.
Para encontrarlo, necesitaríamos profundizar en el cierre de cada lambda, uno de los cuales está enterrado en el otro:
Entonces, si ponemos documentación sobre esta función, o queremos poder decorar funciones que requieren más de un argumento, o simplemente queremos saber qué función estábamos viendo en una sesión de depuración, necesitamos hacer un poco más con nuestro envoltura.
Solución con todas las funciones: superar la mayoría de estos problemas
¡Tenemos el decorador
wraps
delfunctools
módulo en la biblioteca estándar!Es lamentable que todavía haya algo repetitivo, pero esto es tan simple como podemos hacerlo.
En Python 3, también obtienes
__qualname__
y__annotations__
asigna por defecto.Y ahora:
Y ahora:
Conclusión
Entonces vemos que
wraps
hace la función de ajuste haga casi todo excepto decirnos exactamente lo que la función toma como argumentos.Hay otros módulos que pueden intentar resolver el problema, pero la solución aún no está en la biblioteca estándar.
fuente
Para explicar el decorador de una manera simple:
Con:
Cuando hacer:
Realmente lo haces:
fuente
Un decorador toma la definición de la función y crea una nueva función que ejecuta esta función y transforma el resultado.
es equivalente a:
Ejemplo:
Esta
es equivalente a esto
65 <=> 'a'
Para entender al decorador, es importante notar que ese decorador creó una nueva función do que es interna que ejecuta la función y transforma el resultado.
fuente
print(do(65))
yprint(do2(65))
serA
yA
?También puedes escribir decorador en clase
fuente
Esta respuesta ha sido respondida durante mucho tiempo, pero pensé que compartiría mi clase de Decorador, lo que hace que escribir nuevos decoradores sea fácil y compacto.
Por un lado, creo que esto hace que el comportamiento de los decoradores sea muy claro, pero también facilita la definición de nuevos decoradores de manera muy concisa. Para el ejemplo mencionado anteriormente, puede resolverlo como:
También puede usarlo para realizar tareas más complejas, como por ejemplo un decorador que automáticamente hace que la función se aplique de forma recursiva a todos los argumentos en un iterador:
Que imprime:
Tenga en cuenta que este ejemplo no incluyó el
list
tipo en la creación de instancias del decorador, por lo que en la declaración de impresión final el método se aplica a la lista en sí, no a los elementos de la lista.fuente
Aquí hay un ejemplo simple de encadenamiento de decoradores. Tenga en cuenta la última línea: muestra lo que está sucediendo debajo de las cubiertas.
El resultado se ve así:
fuente
Hablando del ejemplo del contador: como se indicó anteriormente, el contador se compartirá entre todas las funciones que usan el decorador:
De esa manera, su decorador puede reutilizarse para diferentes funciones (o usarse para decorar la misma función varias veces:)
func_counter1 = counter(func); func_counter2 = counter(func)
, y la variable de contador seguirá siendo privada para cada uno.fuente
Decora funciones con diferentes números de argumentos:
Resultado:
fuente
def wrapper(*args, **kwargs):
yfn(*args, **kwargs)
.La respuesta de Paolo Bergantino tiene la gran ventaja de usar solo el stdlib, y funciona para este ejemplo simple donde no hay argumentos decoradores ni argumentos de funciones decoradas .
Sin embargo, tiene 3 limitaciones principales si desea abordar casos más generales:
makestyle(style='bold')
decorador no es trivial.@functools.wraps
no conservan la firma , por lo que si se proporcionan argumentos incorrectos, comenzarán a ejecutarse y podrían generar un tipo de error diferente al habitual.TypeError
.@functools.wraps
al acceder a un argumento basado en su nombre . De hecho, el argumento puede aparecer*args
,**kwargs
entrar o no aparecer (si es opcional).Escribí
decopatch
para resolver el primer problema y escribímakefun.wraps
para resolver los otros dos. Tenga en cuenta quemakefun
aprovecha el mismo truco que la famosadecorator
lib.Así es como crearía un decorador con argumentos, devolviendo envoltorios que realmente conservan la firma:
decopatch
le proporciona otros dos estilos de desarrollo que ocultan o muestran los diversos conceptos de Python, según sus preferencias. El estilo más compacto es el siguiente:En ambos casos, puede verificar que el decorador funcione como se espera:
Consulte la documentación para más detalles.
fuente