From e81137474f102d71fe0b25307bcf88810c1b2d43 Mon Sep 17 00:00:00 2001 From: Ekaitz Zarraga Date: Wed, 4 Mar 2020 13:41:02 +0100 Subject: New arrangement for multilanguage and metadata support --- src/04_funciones.md | 654 ---------------------------------------------------- 1 file changed, 654 deletions(-) delete mode 100644 src/04_funciones.md (limited to 'src/04_funciones.md') 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 "", line 1, in -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 - 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 - 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: - - -## 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]: - - -### 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__ -(,) -``` - -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 "", line 1, in -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]: - -### 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: - - -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. -- cgit v1.2.3