summaryrefslogtreecommitdiff
path: root/es/04_funciones.md
blob: 618ccd6ebd811a0fb6dc550cbda00e747675e10a (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
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
# Funciones

El objetivo de este capítulo es que te familiarices con 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(list):
...     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/>


### 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)
increment10(10)   # Returns 20
increment1 = create_incrementer_function(1)
increment1(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.

### `global` y `nonlocal`

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 que aparecen en contextos superiores.

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)
...
>>> f()
2
>>> a
1
```

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 global, 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 global, el padre de todos los contextos.

``` python
>>> a = 1
>>> def f():
...     global a
...     a = 2
...     print(a)
...
>>> f()
2
>>> a
2
```

> NOTA: Te recomiendo, de todas formas, que nunca 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")`.

El caso de `nonlocal` es similar, sin embargo, está diseñado para trabajar en
contextos anidados.  Es decir, en lugar de saltar a acceder a una variable
global, `nonlocal` la busca en cualquier contexto que no sea el actual.
`nonlocal` comienza a buscar las referencias en el contexto padre y va saltando
hacia arriba en la jerarquía en busca de la referencia. Para saber más:
`help("nonlocal")`.

La diferencia principal entre ambas es que `global` puede crear nuevas
referencias, ya que se sabe a qué contexto debe afectar: al global. Sin
embargo, `nonlocal` necesita que la referencia a la que se pretende acceder
esté creada, ya que no es posible saber a qué contexto se pretende acceder.

Las sentencias `global` y `nonlocal` son tramposas, ya que son capaces de
alterar el comportamiento del *scope léxico* y convertirlo en *scope dinámico*
en casos extraños. El *scope dinámico* es el caso opuesto al léxico, en el que
las funciones acceden a valores definidos en el contexto donde se ejecutan, no
donde se crean.

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

Los *decorators* son un concepto que, a pesar de ser bastante concreto, nos
permite descubrir todo el potencial de lo que se acaba de tratar en este
apartado. Sirven para dotar a las funciones de características adicionales.

Por ejemplo, éste es un decorador que permite crear funciones que se ejecutan
en un *thread* independiente. Tiene sentido para realizar acciones de las que
se quiere que se ejecuten por su cuenta sin ralentizar el hilo principal del
programa, como el envío de un email desde un servidor web.

``` python
import threading

def run_in_thread(fn):
    def run(*args, **kwargs):
        t = threading.Thread(target=fn, args=args, kwargs=kwargs)
        t.start()
        return t
    return run

@run_in_thread
def send_mail():
    """
    Envía un email a un usuario, sin esperar confirmación.
    """
    pass
```

Hay muchos detalles que te habrán llamado la atención del ejemplo, el uso de
`@run_in_thread` probablemente sea uno de ellos. Éste es, sin embargo, el
detalle menos importante ya que únicamente se trata de un poco de *syntactic
sugar*.

> NOTA: el *syntactic sugar* son simplificaciones sintácticas que el lenguaje
> define para acortar expresiones muy utilizadas. El ejemplo clásico de
> *syntactic sugar* es:  
> `a += b`  
> Que es equivalente a:  
> `a = a + b`

Los *decorators* pueden entenderse como un envoltorio para una función. No son
más que una función que devuelve otra. En el caso del decorador del ejemplo,
el *decorator* `run_in_thread` es función que recibe otra función como
argumento de entrada y devuelve la función `run`. Este decorador, al aplicarlo
a una función con `@run_in_thread` está haciendo lo siguiente:

``` python
send_mail = run_in_thread(send_mail)
```

> NOTA: `@decorator` es *syntactic sugar* de `fn = decorator(fn)`. Simplemente,
> es más corto y más bonito.

Por lo que la función `send_mail` ya no es lo que creíamos, sino la función
`run`. En el ejemplo, la función `run` llama a la función `fn` de la función
madre (`run` es una *closure*), que resulta ser `send_mail`, a modo de thread
independiente.

Como puedes apreciar, el hecho de capturar todos los posibles argumentos de
entrada en la función `run` permite a `run_in_thread` decorar cualquier
función, sabiendo que funcionará.

El principal problema que los decoradores generan es que la función que hemos
decorado ya no es la que parecía ser, así que su *docstring*, sus argumentos de
entrada, etc. ya no pueden comprobarse desde la REPL usando la ayuda, ya que la
ayuda buscaría la ayuda de la función devuelta por el decorador (`run` en el
ejemplo). Usando `@functools.wraps`[^functools] podemos resolver este
problema.

[^functools]: Puedes leer por qué y cómo en la documentación oficial de
  python:  
  <https://docs.python.org/3/library/functools.html#functools.wraps>

La realidad es que los *decorators* son una forma muy elegante de añadir
funcionalidades a las funciones sin complicar demasiado el código. Permiten
añadir capacidad de depuración, *profiling* y todo tipo de funcionalidades que
se te ocurran.

Este apartado se deja varias cosas en el tintero, como los decoradores con
parámetros de entrada, pero no pretende ser una referencia de cómo se usan,
sino una introducción a un concepto útil que resume perfectamente lo tratado
durante todo el capítulo.

Te animo, como ejercicio, a que analices el decorador `@lru_cache` del módulo
`functools` y comprendas su interés y su funcionamiento. Para leerlo en la
ayuda debes importar el módulo `functools` primero. Como aún no sabes hacerlo,
aquí tienes la receta:

``` python
>>> import functools
>>> help(functools.lru_cache)
```


## Lo que has aprendido

Este capítulo puede que sea el más complejo de todos los que te has encontrado
y te encontrarás. En él has aprendido a declarar y a usar funciones, cosa
sencilla, y todos los conceptos importantes relacionados con ellas. Un
conocimiento que es útil en python, pero que puede ser extendido a casi
cualquier lenguaje.

Tras un sencillo acercamiento al *scope*, has comprendido que las funciones en
python son sólo un valor más, como puede ser un `int`, y que pueden declararse
en cualquier lugar, lo que te abre la puerta a querer declarar funciones
sencillas sin nombre, que se conocen como funciones *lambda*.

Una vez has aclarado que las funciones son ciudadanos de primera clase
(*first-class citizens*) ya estabas preparado para afrontar la realidad del
*scope* donde has tratado los contextos y cómo funcionan definiendo el concepto
del *scope léxico* que, colateralmente, te ha enseñado lo que es una *closure*,
un método para implementarlo. Pero también has tenido ocasión de aprender que
en python es posible crear casos de *scope dinámico* mediante las sentencias
`global` y `nonlocal`, que pueden ser útiles, pero es mejor no abusar de ellas.

Pero no había quedado claro en su momento cómo funcionaban los argumentos de
entrada y las llamadas a las funciones, así que has tenido ocasión de ver por
primera vez lo que es un *callable* en python, aunque se te ha prometido
analizarlo en el futuro. Lo que sí que has tenido ocasión de tratar son los
argumentos *positional* y *keyword*, y cómo se utilizan en todas sus posibles
formas.

Finalmente, para agrupar todo esto en un único concepto, se te han mostrado los
*decorators*, aunque de forma muy general, con el fin de que vieras que todo lo
que se ha tratado en este capítulo aparece en conceptos avanzados y es
necesario entenderlo si quieren llegar a usarse de forma eficiente.