summaryrefslogtreecommitdiff
path: root/src/04_funciones.md
blob: 0151bcdfbfd2a1fa2d2005ed3da819644fc984d6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
# 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 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( 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. 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/>


### 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:

``` python
>>> 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.

``` python
>>> 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.

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.


## 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 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