arrow_back Volver
Inicio keyboard_arrow_right Artículos keyboard_arrow_right Artículo

Closures en Python

Eduardo Ismael Garcia

Full Stack Developer at Código Facilito.

av_timer 4 Min. de lectura

remove_red_eye 13148 visitas

calendar_today 16 Marzo 2020

Sin duda uno de los conceptos más complicados de comprender cuando estamos aprendiendo Python, u otro lenguaje de programación, es el concepto de closures. No dudo que a más de uno no haya causado un par de dolores de cabeza intentando descifrar exactamente qué son, cómo funciona y por supuesto, donde aplicarlos. Es por ello que en esta ocasión me gustaría que le dedicaramos un par de minutos en comprender cómo funcionan los closures en Python, y por su puesto, por que son tan importante conocerlos y dominarlos. 🐍

Bien, sin más dilación comencemos.

Closures

El tema de closures va muy relacionado con el tema de funciones, así que, que les parece si echamos un rápido repaso a ello.

Es Python, por si no lo sabías, las funciones son ciudadanos de primera clase. Esto quiere decir que es posible asignar a una variable una función, de igual forma, una función puede ser utilizada como argumento o puede ser retornada por otra función. 😳

def suma(a, b):
    return 10

def operacion(funcion, a, b):
    return funcion(a, b)

funcion_suma = suma
resultado = operacion(funcion_suma,10, 10)

print(resultado)

En Python, a diferencias de otros lenguajes de programación, las funciones pueden poseer funciones anidadas, sí, como lo lees, funciones anidadas, como si de una condición o un ciclo se tratará. Para definir una función anidada basta con utilizar la palabra reservada def dentro de la función. 😲

def operacion():
    def suma(a, b):
        return a + b

    return suma

resultado = operacion()(10, 20)
print(resultado)

Sí así lo deseamos podemos retornar una función utilizando la palabra reservada return.

Lo interesante de trabajar con funciones anidadas es el alcance de las variables mismas. Trabajaremos con variables locales y no locales, veamos un ejemplo. 😋

def funcion_principal():
    a = 'a'
    b = 'b'

    def funcion_anidada():
        c = 'c'

        print(a)
        print(b)

    funcion_anidada()
    print(c)

En este caso si ejecutamos funcion_principal obtendremos como resultado un error , donde se nos indica que la variable c no se encuentra definida. Esto error es gracias al scope, al alcance que tiene la variable.

Al las variables a y b definirse en un scope superior , al que posee la variable c, estás posee un ciclo de vida mayor, es decir, podrán ser utilizadas dentro de funcion_principal y dentro de la función anidad. Sin embargo, al la variable c definirse dentro de la función anidada tendrá un alcance menor que a y b, solo pudiendo ser utilizada dentro de dicha función dentro de su bloque.

Cómo sabemos, una variable podrá ser utilizada dentro del scope donde fue definida, y de igual forma, podrá ser utilizada en scopes de nivel inferior, pero nunca en un scope de nível superior, como pudimos observar al ejecutar el script anterior.

Ahora, que pasa si dentro de la función anidada modificamos la variable b y fuera de, la imprimimos.

def funcion_principal():
    a = 'a'
    b = 'b'

    def funcion_anidada():
        c = 'c'
        b = 'Cambios de valor'

    funcion_anidada()
    print(b)

Si ejecuto el script nos percatamos que la variable b no sufre cambio alguno. Esto sucede ya que para Python la variable b, dentro de la función anidada, es completamente diferente a la variable b de función funcion_principal. Pero ¿qué pasa si modificamos el orden de impresión? Antes del "cambio" de valor colocamos el print.

def funcion_principal():
    a = 'a'
    b = 'b'

    def funcion_anidada():
        c = 'c'
        print(b)
        b = 'Cambios de valor'

    funcion_anidada()
    print(b)

En este caso, nuevamente, obtendremos un error: local variable 'b' referenced before assignment.

Si queremos indicar a Python que la variable b dentro de la función anidada será la misma variable b del escope superior, entonces haremos uso de la palabra reservada nonlocal.

    def funcion_anidada():
        nonlocal b

        c = 'c'
        print(b)
        b = 'Cambios de valor'

Finalizamos el repaso. Ok, quizás fue una repaso un poco extenso, pero fue necesario para comprender los closures. Ya que verás, un closure no es más que una función la cual puede genear, de forma dinámica, a otra función. Además, que esta nueva función puede acceder a las variables locales aun cuando la primera haya finalizado. Algo complejo ¿no? veamos un ejemplo.

def saludar_usuario(username):
    mensaje = 'Hola ' + username

    def saludar():
        print(mensaje)

    return saludar

En este caso podemos observar como la función saluda_usuario define la variable mensaje además de crear, de forma dinámica, a la función saludar.

Dentro de la función saludar hacemos uso de la variable mensaje, variable declarada en un scope superior. Hasta aquí nada nuevo con lo que hemos explicado, lo interesante bien al momento de ejecutar la función.

funcion = saludar_usuario('Cody')    
funcion()

En este caso obtendremos como salida 'Hola Cody'. Todo funcionando correctamente. Sin embargo hay que darnos cuenta que a pesar que la función saludar_usuario a dejado de ejecutarse, y con ello su scope, es posible seguir accediendo a las variables definidas dentro de ella. Podemos decir que la nueva función tiene memoria, ya que sigue recordando las variables locales y no locales definidas en su momento.

En este caso nuestra función saludar_usuario no es más que un closure, una función la cual crea a su vez a otra de forma dinámica. Esta nueva función tiene memoria, siendo capaz de recordar, y utilizar, las variables lo locales y no locales definidas previamente.

Ok, el concepto y un ejemplo ya lo tenemos, pero ¿en qué caso debemos utilizar los closures? Primero que nada, de forma personal, recomiendo no utilizarlos de forma indiscriminada. Si el problema al que nos enfrentamos es posible resolverlo sin closures, entonces lo hagamos sin ellos. El problema de utlizar closures será que nuestro código se hará difícil de leer y de mantener, aún si comenzamos a trabajar con módulos, paquetes y todo eso. Entonces, ¿cuando utilizarlos? Sin duda haremos uso de los closures cuando trabajamos con decoradores, dando de esta forma mayor flexibilidad a las funciones, teniendo la posibilidad de extender nuevas funcionalidades sin modificar el código fuente.

¿Cuando valdrá la pena implementar un closure? será algo que la experiencia misma te indique. Habrá ocasiones en las que sea necesario y otras donde no tanto.


Bien, esto sería todo por esta ocasión. Y cuentame ¿ya habías escuchado hablar de los closures?, ¿Te fue un tema sencillo de comprender y dominar? me gustaría que nos lo hicieras saber en la sección de comentario; tampoco olvides comentar, que otro concepto te gustaría que explicaremos.