diff options
author | Ekaitz Zarraga <ekaitz@elenq.tech> | 2019-11-23 14:02:37 +0100 |
---|---|---|
committer | Ekaitz Zarraga <ekaitz@elenq.tech> | 2019-11-23 14:02:37 +0100 |
commit | 3719b5345b69b4bf6e7f47a852f863ce7178d4d8 (patch) | |
tree | 29b386725eb01c33f0969ca2ca65f22d6e304ef9 | |
parent | 79dee39bea57fe27a911dcc1cdf8623115323d17 (diff) |
Start with functions
-rw-r--r-- | src/04_funciones.md | 310 |
1 files changed, 310 insertions, 0 deletions
diff --git a/src/04_funciones.md b/src/04_funciones.md new file mode 100644 index 0000000..3e21f97 --- /dev/null +++ b/src/04_funciones.md @@ -0,0 +1,310 @@ +# Funciones + +El objetivo de este capítulo es que sientas comodidad en 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 lanzamos: + +``` +>>> inc(1) +2 +>>> inc(10) +11 +``` + +Si preguntamos por `b`: + +``` +>>> 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( lista ): +... 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. Python las define como +funciones para vagos. + +La sintaxis de una función lambda te la enseño con un ejemplo: + +``` +>>> 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 `filra_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/> + + +### Global + +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 globales. + +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: + +``` +>>> a = 1 +>>> def f(): +... a = 2 +... print(a) +... +>>> a +1 +>>> f() +2 +``` + +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 externa, 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 padre que la albergue. + +``` +>>> a = 1 +>>> def f(): +... global a +... a = 2 +... print(a) +... +>>> f() +2 +>>> a +2 +``` + +> NOTA: Te recomiendo, de todas formas, que no 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")`. + +### 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 ) +increment( 10 ) # Returns 20 +increment1 = create_incrementer_function( 1 ) +increment( 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. + + +## Argumentos de entrada + +### Positional and Keyword Arguments + +#### Defaults + +> WARNING! MUTABLE DEFAULTS + +## Decorators |