summaryrefslogtreecommitdiff
path: root/es/04_funciones.md
diff options
context:
space:
mode:
Diffstat (limited to 'es/04_funciones.md')
-rw-r--r--es/04_funciones.md134
1 files changed, 81 insertions, 53 deletions
diff --git a/es/04_funciones.md b/es/04_funciones.md
index 2fd3c73..4e01e55 100644
--- a/es/04_funciones.md
+++ b/es/04_funciones.md
@@ -327,7 +327,7 @@ que se altere, sino el contexto global, el padre de todos los contextos.
2
```
-> NOTA: Te recomiendo, de todas formas, que nunca edites valores globales desde
+> Te recomiendo, de todas formas, que nunca edites valores globales desde
> el cuerpo de funciones. Es más elegante y comprensible si los efectos de las
> funciones sólo se aprecian en los argumentos de entrada y salida.
@@ -469,7 +469,7 @@ move_file(target="/home/guido/doc.txt", source="file.txt")
# "file.txt" -> "/home/guido/doc.txt"
```
-> NOTA: Si quieres que sean obligatorios, siempre puedes lanzar una excepción.
+> Si quieres que sean obligatorios, siempre puedes lanzar una excepción.
Para funciones que acepten ambos tipos de argumento, es obligatorio declarar e
introducir todos los argumentos posicionales primero. Es lógico, porque son
@@ -534,69 +534,92 @@ 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
+Por ejemplo, éste es un decorador que mide el tiempo de ejecución de una
+función. No lo consideres la forma adecuada para hacerlo porque no es para nada
+preciso, pero es suficiente para entender el funcionamiento de lo que queremos
+representar:
+
+``` {.python .number-lines}
+>>> import time
+>>> def timer(func_to_decorate):
+... def decorated_function(*args, **kwargs):
+... start = time.time()
+... retval = func_to_decorate(*args, **kwargs)
+... end = time.time()
+... print("Function needed:", end - start, "s to execute")
+... return retval
+... return decorated_function
+...
+>>> @timer
+... def my_function(secs):
+... time.sleep(secs)
+... return "whatever"
+...
+>>> my_function(1)
+Function needed: 1.0002844333648682 s to execute
+'whatever'
+>>> my_function(4)
+Function needed: 4.004255533218384 s to execute
+'whatever'
+>>>
```
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*.
+`@timer` probablemente sea uno de ellos. Éste es, sin embargo, el detalle menos
+importante ya que únicamente se trata de un poco de *syntactic
+sugar*[^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`
+[^syntactic-sugar]: 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:
+más que una función que devuelve otra. En el caso del decorador del ejemplo, el
+*decorator* `timer` es función que recibe otra función como argumento de
+entrada y devuelve la función `decorated_function`. Este decorador, al
+aplicarlo a una función con `@timer` está haciendo lo siguiente:
``` python
-send_mail = run_in_thread(send_mail)
+my_function = timer(my_function)
```
-> NOTA: `@decorator` es *syntactic sugar* de `fn = decorator(fn)`. Simplemente,
+> `@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.
+Por lo que la función `my_function` ya no es lo que creíamos. Se ha sobrescrito
+con `decorated_function`. En el ejemplo, la función `decorated_function` llama
+a la función `function_to_decorate` de la función madre (`decorated_function`
+es una *closure*). Cuando se aplica el decorador sobre `my_function`, la
+función `function_to_decorate` es `my_function` para esa *closure*.
+
+La línea `5` del ejemplo traslada los argumentos capturados en la función
+`decorated_function` a la función `function_to_decorate` usando los mismos
+asteriscos que se utilizan para declarar la captura de argumentos. Estos
+asteriscos son necesarios porque los argumentos en `args` y `kwargs` están
+guardados en una tupla y un diccionario respectivamente y deben desempaquetarse
+para que se inserten sus contenidos como argumentos de la función. De no
+aplicarse, se llamaría a la función con dos argumentos: una tupla y un
+diccionario.
+
+Al capturar todos los argumentos y pasarlos tal y como se recibieron a la
+función decorada, el decorador `timer` es transparente para la función. La
+función cree que ha sido llamada de forma directa aunque se haya añadido
+funcionalidad alrededor de ésta que es capaz de contar el tiempo de ejecución.
+
+Si se desea, se pueden alterar `args` y `kwargs` para transformar los
+argumentos de la llamada a la función, pero es algo que debe hacerse
+cuidadosamente porque deformar las llamadas a las funciones puede confundir a
+quien aplique el decorador.
+
+Los decoradores, usados de forma tan cruda, añaden ciertos problemas. El
+principal 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 (`decorated_function` 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:
@@ -622,6 +645,11 @@ aquí tienes la receta:
>>> help(functools.lru_cache)
```
+A parte de los decoradores de funciones, python permite decorar clases. No son
+difíciles de entender una vez se conocen los decoradores de funciones así que
+te animo a que los investigues cuando hayas estudiado la programación orientada
+a objetos en el capítulo siguiente.
+
## Lo que has aprendido