summaryrefslogtreecommitdiff
path: root/src/04_funciones.md
blob: eee27fb97372028dff88b9116221dc2b1aa8a93c (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
# 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