summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEkaitz Zarraga <ekaitz@elenq.tech>2019-11-23 14:02:37 +0100
committerEkaitz Zarraga <ekaitz@elenq.tech>2019-11-23 14:02:37 +0100
commit3719b5345b69b4bf6e7f47a852f863ce7178d4d8 (patch)
tree29b386725eb01c33f0969ca2ca65f22d6e304ef9
parent79dee39bea57fe27a911dcc1cdf8623115323d17 (diff)
Start with functions
-rw-r--r--src/04_funciones.md310
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