summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEkaitz Zarraga <ekaitz@elenq.tech>2019-11-26 13:59:30 +0100
committerEkaitz Zarraga <ekaitz@elenq.tech>2019-11-26 13:59:30 +0100
commit678e0c8f1b1f96f5184e8934c52c2fec62be8d9c (patch)
treeacb058480f52c06969d532e3366f3b1a5a047389
parentf5807a54591563415a09ca81d45bd4bdd500a410 (diff)
decorators in functions
-rw-r--r--src/04_funciones.md94
1 files changed, 94 insertions, 0 deletions
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:
+ <https://docs.python.org/3/library/functools.html#functools.wraps>
+
+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