summaryrefslogtreecommitdiff
path: root/src/04_funciones.md
diff options
context:
space:
mode:
authorEkaitz Zarraga <ekaitz@elenq.tech>2020-03-04 13:41:02 +0100
committerEkaitz Zarraga <ekaitz@elenq.tech>2020-03-04 13:41:02 +0100
commite81137474f102d71fe0b25307bcf88810c1b2d43 (patch)
tree840a184cf0b9c08a84c25caf390e4b7696589c9a /src/04_funciones.md
parent403a94590f02479f7be41bbb62aae972e0ab596a (diff)
New arrangement for multilanguage and metadata support
Diffstat (limited to 'src/04_funciones.md')
-rw-r--r--src/04_funciones.md654
1 files changed, 0 insertions, 654 deletions
diff --git a/src/04_funciones.md b/src/04_funciones.md
deleted file mode 100644
index 618ccd6..0000000
--- a/src/04_funciones.md
+++ /dev/null
@@ -1,654 +0,0 @@
-# Funciones
-
-El objetivo de este capítulo es que te familiarices con el uso de las
-funciones. Parece sencillo pero es una tarea un tanto complicada porque, visto
-como nos gusta hacer las cosas, tenemos una gran cantidad de complejidad que
-abordar.
-
-Antes de entrar, vamos a definir una función y a usarla un par de veces:
-
-``` python
-def inc(a):
- b = a + 1
- return b
-```
-
-Si la llamamos:
-
-``` python
->>> inc(1)
-2
->>> inc(10)
-11
-```
-
-Cuidado con las declaraciones internas en las funciones. Si preguntamos por
-`b`:
-
-``` python
->>> b
-Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
-NameError: name 'b' is not defined
-```
-
-Parece que no conoce el nombre `b`. Esto es un tema relacionado con el *scope*.
-
-## Scope
-
-Anteriormente se ha dicho que python es un lenguaje de programación con gestión
-automática de la memoria. Esto significa que él mismo es capaz de saber cuando
-necesita pedir más memoria al sistema operativo y cuando quiere liberarla.
-El *scope* es un resultado este sistema. Para que python pueda liberar
-memoria, necesita de un proceso conocido como *garbage collector* (recolector
-de basura), que se encarga de buscar cuando las referencias ya no van a poder
-usarse más para pedir una liberación de esa memoria. Por tanto, las referencias
-tienen un tiempo de vida, desde que se crean hasta que el recolector de basura
-las elimina. Ese tiempo de vida se conoce como *scope* y, más que en tiempo, se
-trata en términos de espacio en el programa.
-
-El recolector de basura tiene unas normas muy estrictas y conociéndolas es
-fácil saber en qué espacio se puede mover una referencia sin ser disuelta.
-
-Resumiendo mucho, las referencias que crees se mantienen vivas hasta que la
-función termine. Como en el caso de arriba la función en la que se había creado
-`b` había terminado, `b` había sido limpiada por el recolector de basura. `b`
-era una referencia *local*, asociada a la función `inc`.
-
-Puede haber referencias declaradas fuera de cualquier función, que se llaman
-*globales*. Éstas se mantienen accesibles desde cualquier punto del programa, y
-se mantienen vivas hasta que éste se cierre. Considera que el propio programa
-es una función gigante que engloba todo.
-
-Python define que cualquier declaración está disponible
-en bloques internos, pero no al revés. El siguiente ejemplo lo muestra:
-
-``` python
-c = 100
-def funcion():
- a = 1
- # Se conoce c aquí dentro
-# Aquí fuera no se conoce a
-```
-
-El *scope* es peculiar en algunos casos que veremos ahora, pero mientras tengas
-claro que se extiende hacia dentro y no hacia fuera, todo irá bien.
-
-## First-class citizens
-
-Antes de seguir jugando con el *scope*, necesitas saber que las funciones en
-python son lo que se conoce como *first-class citizens* (ciudadanos de primera
-clase). Esto significa que pueden hacer lo mismo que cualquier otro valor.
-
-Las funciones son un valor más del sistema, como puede ser un string, y su
-nombre no es más que una referencia a ellas.
-
-Por esto mismo, pueden ser enviadas como argumento de entrada a otras
-funciones, devueltas con sentencias `return` o incluso ser declaradas dentro de
-otras funciones.
-
-Por ejemplo:
-
-``` python
->>> def filtra_lista(list):
-... def mayor_que_4(a):
-... return a > 4
-... return list( filter(mayor_que_4, lista) )
-...
->>> filtra_lista( [1,2,3,4,5,6,7] )
-[5, 6, 7]
-```
-
-En este ejemplo, haciendo uso de la función `filter` (usa la ayuda para ver lo
-que hace), filtramos todos los elementos mayores que `4` de la lista. Pero para
-ello hemos creado una función que sirve para compararlos y se la hemos
-entregado a la función `filter`.
-
-Este ejemplo no tiene más interés que intentar enseñarte que puedes crear
-funciones como cualquier otro valor y asignarles un nombre, para después
-pasarlas como argumento de entrada a otra función.
-
-## Lambdas
-
-Las funciones *lambda*[^lambda] o funciones anónimas son una forma sencilla de
-declarar funciones simples sin tener que escribir tanto. La documentación
-oficial de python las define como funciones para vagos.
-
-La sintaxis de una función lambda te la enseño con un ejemplo:
-
-``` python
->>> lambda x,y: x + y
-<function <lambda> at 0x7f035b879950>
->>> (lambda x,y: x + y)(1,2)
-3
-```
-
-En el ejemplo primero se muestra la declaración de una función y después
-colocando los paréntesis de precedencia y después de llamada a función se
-construye una función a la izquierda y se ejecuta con los valores `1` y `2`.
-
-Es fácil de entender la sintaxis de la función lambda, básicamente es una
-función reducida de sólo una sentencia con un `return` implícito.
-
-El ejemplo de la función `filtra_lista` puede reducirse mucho usando una
-función lambda:
-
-``` python
->>> def filtra_lista( lista ):
-... return list( filter(lambda x: x > 4, lista) )
-...
->>> filtra_lista( [1,2,3,4,5,6,7] )
-[5, 6, 7]
-```
-
-No necesitábamos una función con nombre en este caso, porque sólo iba a
-utilizarse esta vez, así que resumimos y reducimos tecleos.
-
-De todos modos, podemos asignarlas a una referencia para poder repetir su uso:
-
-``` python
->>> f = lambda x: x + 1
->>> f(1)
-2
->>> f(10)
-11
->>> f
-<function <lambda> at 0x7f02184febf8>
-```
-
-Las funciones lambda se usan un montón como *closure*, un concepto donde el
-*scope* se trabaja más allá de lo que hemos visto. Sigamos visitando el
-*scope*, para entender sus usos más en detalle.
-
-[^lambda]: Toman su nombre del Lambda
- Calculus:
- <https://en.wikipedia.org/wiki/Deductive_lambda_calculus>
-
-## Scope avanzado
-
-Cada vez que se crea una función, python crea un nuevo contexto para ella.
-Puedes entender el concepto de contexto como una tabla donde se van guardando
-las referencias que se declaran en la función. Cuando la función termina, su
-contexto asociado se elimina, y el recolector de basura se encarga de liberar
-la memoria de sus variables, tal y como vimos anteriormente.
-
-Lo que ocurre es que estos contextos son jerárquicos, por lo que, al crear una
-función, el padre del contexto que se crea es el contexto de la función madre.
-Python utiliza esto como método para encontrar las referencias. Si una
-referencia no se encuentra en el contexto actual, python la buscará en el
-contexto padre y así sucesivamente hasta encontrarla o lanzar un error diciendo
-que no la conoce. Esto explica por qué las variables declaradas en la función
-madre pueden encontrarse y accederse y no al revés.
-
-Aunque hemos explicado el *scope* como un concepto asociado a las funciones, la
-realidad es que hay varias estructuras que crean nuevos contextos en python. El
-comportamiento sería el mismo del que se ha hablado anteriormente, las
-referencias que se creen en ellos no se verán en el *scope* de nivel superior,
-pero sí al revés. Los casos son los siguientes:
-
-- Los módulos. Ver capítulo correspondiente
-- Las clases. Ver capítulo de Programación Orientada a Objetos.
-- Las funciones, incluidas las funciones anónimas o lambda.
-- Las expresiones generadoras[^generator-expression], que normalmente se
- encuentran en las *list-comprehension* que ya se han tratado en el capítulo
- previo.
-
-[^generator-expression]: <https://www.python.org/dev/peps/pep-0289/>
-
-
-### Scope léxico, Closures
-
-Hemos dicho que las funciones pueden declararse dentro de funciones, pero no
-hemos hablado de qué ocurre con el *scope* cuando la función declarada se
-devuelve y tiene una vida más larga que la función en la que se declaró. El
-siguiente ejemplo te pone en contexto:
-
-``` python
-def create_incrementer_function(increment):
- def incrementer (val):
- # Recuerda que esta función puede ver el valor `increment` por
- # por haber nacido en un contexto superior.
- return val + increment
- return incrementer
-
-increment10 = create_incrementer_function(10)
-increment10(10) # Returns 20
-increment1 = create_incrementer_function(1)
-increment1(10) # Returns 11
-```
-En este ejemplo hemos creado una función que construye funciones que sirven
-para incrementar valores.
-
-Las funciones devueltas viven durante más tiempo que la función que las
-albergaba por lo que saber qué pasa con la variable `increment` es difícil a
-simple vista.
-
-Python no destruirá ninguna variable que todavía pueda ser accedida, si lo
-hiciera, las funciones devueltas no funcionarían porque no podrían incrementar
-el valor. Habrían olvidado con qué valor debían incrementarlo.
-
-Para que esto pueda funcionar, las funciones guardan el contexto del momento de
-su creación, así que la función `incrementer` recuerda la primera vez que fue
-construida en un contexto en el que `increment` valía `10` y la nueva
-`incrementer` creada en la segunda ejecución de `create_incrementer_function`
-recuerda que cuando se creó `increment` tomó el valor `1`. Ambas funciones son
-independientes, aunque se llamen de la misma forma en su concepción, no se
-pisaron la una a la otra, porque pertenecían a contextos distintos ya que la
-función que las creaba terminó y luego volvió a iniciarse.
-
-Este funcionamiento donde el comportamiento de las funciones depende del lugar
-donde se crearon y no del contexto donde se ejecutan se conoce como *scope
-léxico*.
-
-Las *closures* son una forma de implementar el *scope léxico* en un lenguaje
-cuyas funciones sean *first-class citizens*, como es el caso de python, y su
-funcionamiento se basa en la construcción de los contextos y su asociación a
-una función capaz de recordarlos aunque la función madre haya terminado.
-
-Python analiza cada función y revisa qué referencias del contexto superior
-deben mantenerse en la función. Si encuentra alguna, las asocia a la propia
-función creando así lo que se conoce como *closure*, una función que recuerda
-una parte del contexto. No todas las funciones necesitan del contexto previo
-así que sólo se crean *closures* en función de lo necesario.
-
-Puedes comprobar si una función es una *closure* analizando su campo
-`__closure__`. Si no está vacío (valor `None`), significará que la función es
-una *closure* como la que ves a continuación. Una *closure* que recuerda un
-*int* del contexto padre:
-
-``` python
->>> f.__closure__
-(<cell at 0x7f04b4ebfa68: int object at 0xa68ac0>,)
-```
-
-Lo que estás viendo lo entenderás mejor cuando llegues al apartado de
-programación orientada a objetos. Pero, para empezar, ves que contiene una
-tupla con una `cell` de tipo *integer*.
-
-A nivel práctico, las *closures* son útiles para muchas labores que iremos
-desgranando de forma accidental. Si tienes claro el concepto te darás cuenta
-dónde aparecen en los futuros ejemplos.
-
-### `global` y `nonlocal`
-
-Hemos hablado de qué sentencias crean nuevos contextos, pero no hemos hablado
-de qué pasa si esos nuevos contextos crean referencias cuyo nombre es idéntico
-al de las referencias que aparecen en contextos superiores.
-
-Partiendo de lo que se acaba de explicar, y antes de adentrarnos en ejemplos,
-si se crea una función (o cualquiera de las otras estructuras) python creará un
-contexto para ella. Una vez creado, al crear una variable en este nuevo
-contexto, python añadirá una nueva entrada en su tabla hija con el nombre de la
-variable. Al intentar consultarla, python encontrará que en su tabla hija
-existe la variable y tomará el valor con el que la declaramos. Cuando la
-función termine, la tabla de contexto asociada a la función será eliminada.
-Esto siempre es así, independientemente del nombre de referencia que hayamos
-seleccionado. Por tanto, si el nombre ya existía en alguno de los contextos
-padre, lo ocultaremos, haciendo que dentro de esta función se encuentre el
-nombre recién declarado y no se llegue a buscar más allá. Cuando la función
-termine, como el contexto asociado a ésta no está en la zona de búsqueda de la
-función madre, en la función madre el valor seguirá siendo el que era.
-
-Ilustrándolo en un ejemplo:
-
-``` python
->>> a = 1
->>> def f():
-... a = 2
-... print(a)
-...
->>> f()
-2
->>> a
-1
-```
-
-Aunque el nombre de la referencia declarada en el interior sea el mismo que el
-de una referencia externa su declaración no afecta, lógicamente, al exterior
-ya que ocurre en un contexto independiente.
-
-Para afectar a la referencia global, python dispone de la sentencia `global`.
-La sentencia `global` afecta al bloque de código actual, indicando que los
-identificadores listados deben interpretarse como globales. De esta manera, si
-se reasigna una referencia dentro de la función, no será el contexto propio el
-que se altere, sino el contexto global, el padre de todos los contextos.
-
-``` python
->>> a = 1
->>> def f():
-... global a
-... a = 2
-... print(a)
-...
->>> f()
-2
->>> a
-2
-```
-
-> NOTA: 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.
-
-Para más detalles sobre limitaciones y excepciones, puedes buscar en la ayuda
-ejecutando `help("global")`.
-
-El caso de `nonlocal` es similar, sin embargo, está diseñado para trabajar en
-contextos anidados. Es decir, en lugar de saltar a acceder a una variable
-global, `nonlocal` la busca en cualquier contexto que no sea el actual.
-`nonlocal` comienza a buscar las referencias en el contexto padre y va saltando
-hacia arriba en la jerarquía en busca de la referencia. Para saber más:
-`help("nonlocal")`.
-
-La diferencia principal entre ambas es que `global` puede crear nuevas
-referencias, ya que se sabe a qué contexto debe afectar: al global. Sin
-embargo, `nonlocal` necesita que la referencia a la que se pretende acceder
-esté creada, ya que no es posible saber a qué contexto se pretende acceder.
-
-Las sentencias `global` y `nonlocal` son tramposas, ya que son capaces de
-alterar el comportamiento del *scope léxico* y convertirlo en *scope dinámico*
-en casos extraños. El *scope dinámico* es el caso opuesto al léxico, en el que
-las funciones acceden a valores definidos en el contexto donde se ejecutan, no
-donde se crean.
-
-## Argumentos de entrada y llamadas
-
-Los argumentos de entrada se definen en la declaración de la función y se ha
-dado por hecho que es evidente que se separan por comas (`,`) y que, a la hora
-de llamar a la función, deben introducirse en el orden en el que se han
-declarado. Por mucho que esto sea cierto, requiere de una explicación más
-profunda.
-
-### Callable
-
-En python las funciones son un tipo de *callable*, «cosa que puede ser llamada»
-en inglés. Esto significa, de algún modo que hay otras cosas que pueden ser
-llamadas que no sean funciones. Y así es.
-
-Para python cualquier valor que soporte la aplicación de los paréntesis se
-considera «llamable». En el apartado de programación orientada a objetos
-entenderás esto en detalle. De momento, piensa que, igual que pasa al acceder a
-los campos de una colección usando los corchetes, siempre que python se
-encuentre unos paréntesis después de un valor tratará de ejecutar el valor. Así
-que los paréntesis no son una acción que únicamente pueda aplicarse en nombres
-de función[^lambdas-ejemplo] y python no lanzará un fallo de sintaxis cuando
-los usemos fuera de lugar, si no que será un fallo de tiempo de ejecución al
-darse cuenta de lo que se intenta ejecutar no es ejecutable.
-
-``` python
->>> 1()
-Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
-TypeError: 'int' object is not callable
-```
-
-[^lambdas-ejemplo]: Aunque en realidad esto ya lo has visto en los ejemplos de
- las funciones lambda.
-
-#### Caso de estudio: Switch Case
-
-Si quieres ver un ejemplo avanzado de esto, te propongo la creación de la
-estructura *switch-case* [^switch-case], que puede encontrarse en otros
-lenguajes, pero que en lugar de usar una estructura basada en un *if* con
-múltiples *elif* uses un diccionario de funciones.
-
-Las funciones son valores, por lo que pueden ocupar un diccionario como
-cualquier otro valor. Construyendo un diccionario en cuyas claves se encuentran
-los casos del *switch-case* y en cuyos valores se encuentran sus funciones
-asociadas se puede crear una sentencia con el mismo comportamiento.
-
-En el siguiente ejemplo se plantea una aplicación por comandos. Captura el
-tecleo del usuario y ejecuta la función asociada al comando. Las funciones no
-están escritas, pero puedes completarlas y analizar su comportamiento. Las
-palabras que no entiendas puedes consultarlas en la ayuda.
-
-``` python
-def borrar(*args):
- pass
-def crear(*args):
- pass
-def renombrar(*args):
- pass
-
-casos = {
- "borrar": borrar,
- "crear": crear,
- "renombrar": renombrar
-}
-
-comando = input("introduce el comando> ")
-
-try:
- casos[comando]()
-except KeyError:
- print("comando desconocido")
-
-```
-
-[^switch-case]: <https://en.wikipedia.org/wiki/Switch_statement>
-
-### Positional vs Keyword Arguments
-
-Las funciones tienen dos tipos de argumentos de entrada, aunque sólo hayamos
-mostrado uno de ellos de momento.
-
-El que ya conoces se denomina *positional argument* y se refiere a que son
-argumentos que se definen en función de su posición. Los argumentos
-posicionales deben ser situados siempre en el mismo orden, si no, los
-resultados de la función serán distintos. Las referencias `source` y `target`
-toman el primer argumento y el segundo respectivamente. Darles la vuelta
-resulta en el resultado opuesto al que se pretendía.
-
-``` python
-def move_file ( source, target ):
- "Mueve archivo de `source` a `target"
- pass
-
-move_file("file.txt", "/home/guido/doc.txt")
- # "file.txt" -> "/home/guido/doc.txt"
-move_file("/home/guido/doc.txt", "file.txt")
- # "/home/guido/doc.txt"-> "file.txt"
-```
-
-Los *keyword argument* o argumentos con nombre, por otro lado, se comportan
-como un diccionario. Su orden no importa pero es necesario marcarlos con su
-respectiva clave. Además, son opcionales porque en el momento de la declaración
-de la función python te obliga a que les asocies un valor por defecto
-(*default*). En el siguiente ejemplo se convierte la función a una basada en
-argumentos con nombre. No se han utilizado valores por defecto especiales, pero
-pueden usarse otros.
-
-``` python
-def move_file( source=None, target=None):
- "Mueve archivo de `source` a `target"
- pass
-
-move_file(source="file.txt", target="/home/guido/doc.txt")
- # "file.txt" -> "/home/guido/doc.txt"
-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.
-
-Para funciones que acepten ambos tipos de argumento, es obligatorio declarar e
-introducir todos los argumentos posicionales primero. Es lógico, porque son
-los que requieren de una posición.
-
-También es posible declarar funciones que acepten cualquier cantidad de
-argumentos de un tipo u otro. Ésta es la sintaxis:
-
-``` python
-def argument_catcher( *args, **kwargs )
- "Función ejecutable con cualquier número de argumentos de entrada, tanto
- posicionales como con nombre."
- print( args )
- print( kwargs )
-```
-
-Los nombres `args` y `kwargs` son convenciones que casi todos los programadores
-de python utilizan, pero puedes seleccionar los que quieras. Lo importante es
-usar `*` para los argumentos posicionales y `**` para los argumentos con
-nombre.
-
-Prueba a ejecutar la función del ejemplo, verás que los argumentos posicionales
-se capturan en una tupla y los argumentos con nombre en un diccionario.
-
-Este tipo de funciones multiargumento se utilizan mucho en los *decorators*,
-caso que estudiaremos al final de este capítulo.
-
-#### Peligro: Mutable Defaults
-
-Existe un caso en el que tienes que tener mucho cuidado. Los valores por
-defecto en los argumentos con nombre se memorizan de una ejecución de la
-función a otra. En caso de que sean valores inmutables no tendrás problemas,
-porque su valor nunca cambiará, pero si almacenas en ellos valores mutables y
-los modificas, la próxima vez que ejecutes la función los valores por defecto
-habrán cambiado.
-
-La razón por la que los valores por defecto se recuerdan es que esos valores se
-construyen en la creación de la función, no en su llamada. Lógicamente, puesto
-que es en la sentencia `def` donde aparecen.
-
-``` python
->>> def warning(default=[]):
-... default.append(1)
-... return default
-...
->>> warning()
-[1]
->>> warning()
-[1, 1]
->>> warning()
-[1, 1, 1]
->>> warning()
-[1, 1, 1, 1]
-```
-
-## 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
-
-Este capítulo puede que sea el más complejo de todos los que te has encontrado
-y te encontrarás. En él has aprendido a declarar y a usar funciones, cosa
-sencilla, y todos los conceptos importantes relacionados con ellas. Un
-conocimiento que es útil en python, pero que puede ser extendido a casi
-cualquier lenguaje.
-
-Tras un sencillo acercamiento al *scope*, has comprendido que las funciones en
-python son sólo un valor más, como puede ser un `int`, y que pueden declararse
-en cualquier lugar, lo que te abre la puerta a querer declarar funciones
-sencillas sin nombre, que se conocen como funciones *lambda*.
-
-Una vez has aclarado que las funciones son ciudadanos de primera clase
-(*first-class citizens*) ya estabas preparado para afrontar la realidad del
-*scope* donde has tratado los contextos y cómo funcionan definiendo el concepto
-del *scope léxico* que, colateralmente, te ha enseñado lo que es una *closure*,
-un método para implementarlo. Pero también has tenido ocasión de aprender que
-en python es posible crear casos de *scope dinámico* mediante las sentencias
-`global` y `nonlocal`, que pueden ser útiles, pero es mejor no abusar de ellas.
-
-Pero no había quedado claro en su momento cómo funcionaban los argumentos de
-entrada y las llamadas a las funciones, así que has tenido ocasión de ver por
-primera vez lo que es un *callable* en python, aunque se te ha prometido
-analizarlo en el futuro. Lo que sí que has tenido ocasión de tratar son los
-argumentos *positional* y *keyword*, y cómo se utilizan en todas sus posibles
-formas.
-
-Finalmente, para agrupar todo esto en un único concepto, se te han mostrado los
-*decorators*, aunque de forma muy general, con el fin de que vieras que todo lo
-que se ha tratado en este capítulo aparece en conceptos avanzados y es
-necesario entenderlo si quieren llegar a usarse de forma eficiente.