summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEkaitz Zarraga <ekaitz@elenq.tech>2019-11-24 19:19:24 +0100
committerEkaitz Zarraga <ekaitz@elenq.tech>2019-11-24 19:19:24 +0100
commitfa0053899fc12744553aeaf3927d7d6b962a314d (patch)
treefb79a26659138c90cde041d2db98cf62c4f60b61
parent4d73b41553d9284f8cd79fa954dc3ea662ec0ee5 (diff)
functions almost done
-rw-r--r--src/04_funciones.md228
1 files changed, 207 insertions, 21 deletions
diff --git a/src/04_funciones.md b/src/04_funciones.md
index 7cee36c..0151bcd 100644
--- a/src/04_funciones.md
+++ b/src/04_funciones.md
@@ -13,7 +13,7 @@ def inc(a):
return b
```
-Si lanzamos:
+Si la llamamos:
``` python
>>> inc(1)
@@ -22,7 +22,8 @@ Si lanzamos:
11
```
-Si preguntamos por `b`:
+Cuidado con las declaraciones internas en las funciones. Si preguntamos por
+`b`:
``` python
>>> b
@@ -93,7 +94,7 @@ Por ejemplo:
... 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]
```
@@ -135,7 +136,7 @@ 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]
```
@@ -219,9 +220,9 @@ Ilustrándolo en un ejemplo:
``` python
>>> a = 1
>>> def f():
-... a = 2
+... a = 2
... print(a)
-...
+...
>>> a
1
>>> f()
@@ -242,9 +243,9 @@ que se altere, sino el contexto padre que la albergue.
>>> a = 1
>>> def f():
... global a
-... a = 2
+... a = 2
... print(a)
-...
+...
>>> f()
2
>>> a
@@ -300,25 +301,210 @@ 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* y esta forma de implementarlo, haciendo que cada función recuerde el
-contexto en el que se creó se denomina *closure*.
+léxico*.
-Concretamente, 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.
+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.
-A nivel práctico, las *closures* son útiles.
+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.
-TODO
+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:
-## Argumentos de entrada
+``` 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.
+
+
+## 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")
+
+```
-### Positional and Keyword Arguments
+[^switch-case]: <https://en.wikipedia.org/wiki/Switch_statement>
-#### Defaults
+### Positional vs Keyword Arguments
-> WARNING! MUTABLE DEFAULTS
+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 editas, 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