From 678e0c8f1b1f96f5184e8934c52c2fec62be8d9c Mon Sep 17 00:00:00 2001 From: Ekaitz Zarraga Date: Tue, 26 Nov 2019 13:59:30 +0100 Subject: decorators in functions --- src/04_funciones.md | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) (limited to 'src') diff --git a/src/04_funciones.md b/src/04_funciones.md index f68781d..47577d7 100644 --- a/src/04_funciones.md +++ b/src/04_funciones.md @@ -508,3 +508,97 @@ que es en la sentencia `def` donde aparecen. ``` ## Decorators + +Los *decorators* son un concepto que, a pesar de ser bastante concreto, nos +permite descubrir todo el potencial de lo que se acaba de tratar en este +apartado. Sirven para dotar a las funciones de características adicionales. + +Por ejemplo, éste es un decorador que permite crear funciones que se ejecutan +en un *thread* independiente. Tiene sentido para realizar acciones de las que +se quiere que se ejecuten por su cuenta sin ralentizar el hilo principal del +programa, como el envío de un email desde un servidor web. + +``` python +import threading + +def run_in_thread(fn): + def run(*args, **kwargs): + t = threading.Thread(target=fn, args=args, kwargs=kwargs) + t.start() + return t + return run + +@run_in_thread +def send_mail(): + """ + Envía un email a un usuario, sin esperar confirmación. + """ + pass +``` + +Hay muchos detalles que te habrán llamado la atención del ejemplo, el uso de +`@run_in_thread` probablemente sea uno de ellos. Éste es, sin embargo, el +detalle menos importante ya que únicamente se trata de un poco de *syntactic +sugar*. + +> NOTA: el *syntactic sugar* son simplificaciones sintácticas que el lenguaje +> define para acortar expresiones muy utilizadas. El ejemplo clásico de +> *syntactic sugar* es: +> `a += b` +> Que es equivalente a: +> `a = a + b` + +Los *decorators* pueden entenderse como un envoltorio para una función. No son +más que una función que devuelve otra. En el caso del decorador del ejemplo, +el *decorator* `run_in_thread` es función que recibe otra función como +argumento de entrada y devuelve la función `run`. Este decorador, al aplicarlo +a una función con `@run_in_thread` está haciendo lo siguiente: + +``` python +send_mail = run_in_thread(send_mail) +``` + +> NOTA: `@decorator` es *syntactic sugar* de `fn = decorator(fn)`. Simplemente, +> es más corto y más bonito. + +Por lo que la función `send_mail` ya no es lo que creíamos, sino la función +`run`. En el ejemplo, la función `run` llama a la función `fn` de la función +madre (`run` es una *closure*), que resulta ser `send_mail`, a modo de thread +independiente. + +Como puedes apreciar, el hecho de capturar todos los posibles argumentos de +entrada en la función `run` permite a `run_in_thread` decorar cualquier +función, sabiendo que funcionará. + +El principal problema que los decoradores generan es que la función que hemos +decorado ya no es la que parecía ser, así que su *docstring*, sus argumentos de +entrada, etc. ya no pueden comprobarse desde la REPL usando la ayuda, ya que la +ayuda buscaría la ayuda de la función devuelta por el decorador (`run` en el +ejemplo). Usando `@functools.wraps`[^functools] podemos resolver este +problema. + +[^functools]: Puedes leer por qué y cómo en la documentación oficial de python: + + +La realidad es que los *decorators* son una forma muy elegante de añadir +funcionalidades a las funciones sin complicar demasiado el código. Permiten +añadir capacidad de depuración, *profiling* y todo tipo de funcionalidades que +se te ocurran. + +Este apartado se deja varias cosas en el tintero, como los decoradores con +parámetros de entrada, pero no pretende ser una referencia de cómo se usan, +sino una introducción a un concepto útil que resume perfectamente lo tratado +durante todo el capítulo. + +Te animo, como ejercicio, a que analices el decorador `@lru_cache` del módulo +`functools` y comprendas su interés y su funcionamiento. Para leerlo en la +ayuda debes importar el módulo `functools` primero. Como aún no sabes hacerlo, +aquí tienes la receta: + +``` python +>>> import functools +>>> help(functools.lru_cache) +``` + + +## Lo que has aprendido -- cgit v1.2.3