# 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 "", line 1, in 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 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 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]: ### 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__ (,) ``` 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 "", line 1, in 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]: ### 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