From f2df77bce2c03910aa3c031405e43b14333bac8e Mon Sep 17 00:00:00 2001 From: Ekaitz Zarraga Date: Wed, 22 Jul 2020 20:01:38 +0200 Subject: Corrections everywhere --- es/04_funciones.md | 134 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 81 insertions(+), 53 deletions(-) (limited to 'es/04_funciones.md') 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 -- cgit v1.2.3