Python: El calificador de ámbito nonlocal

Objetivo: presentar el calificador de ámbito de variable nonlocal.

En la anterior entrega presentamos el concepto de ámbito de una variable y llegamos a una serie de conclusiones:

– Una variable creada en el interior de una función es local y solo existe dentro de ella.

– Las variables creadas a nivel de módulo son globales en el sentido de que son accesibles, además de por el propio módulo, por todas las funciones definidas dentro de él. No obstante, solo son modificables por el propio módulo; las funciones sólo pueden consultar su valor.

– Para modificar una variable global desde dentro de una función es necesario que aparezca declarada como global en el cuerpo de la función.

Vamos a introducir un nuevo nivel de complejidad atendiendo a una característica muy interesante de Python: una función puede incluir definiciones de otras funciones; es decir, la definición de función es anidable.

El Ejemplo 1 ilustra esto de un modo sencillo:

# Ejemplo 1

def f1():
    
    def f2():
        nivel2 = 2
        print(nivel0, nivel1, nivel2)
        
    nivel1 = 1
    f2()
    print(nivel0, nivel1)

nivel0 = 0
f1()
print(nivel0)

Observa que la definición de la función f1() incluye la definición de la función f2().

He definido tres variables en ámbitos distintos: nivel0 a nivel de módulo, nivel1 dentro de la función f1() y nivel2 dentro de f2(), que a su vez está anidada dentro de f1().

Comencemos analizando la función más interior, f2(). En ella se crea la variable local nivel2 y se imprime su valor, junto al de las variables nivel0 y nivel1, que pertenecen, respectivamente, al módulo y al ámbito de f1().

Esto ilustra un concepto importante: una función puede acceder como consulta a todas las variables de los ámbitos en los que está contenida su definición. f2() está contenida en f1(), que a su vez lo está en el módulo principal.

La función f1(), además de definir f2(), crea la variable local nivel1, invoca a f2() e imprime las variables nivel0 y nivel1.

Finalmente, el módulo principal define f1(), crea nivel0, invoca f1() e imprime a continuación el valor de nivel0.

Este es el resultado de la ejecución. Es recomendable que sigas paso a paso su ejecución para asegurarte de que entiendes esta salida.

>>> 
0 1 2
0 1
0

Hay que tener muy claro el sentido de la visibilidad. Desde el módulo principal, cualquier intento de acceder a nivel1 o a nivel2 resultaría en error, del mismo modo que desde f1() tampoco podríamos acceder a nivel2. Por el contrario, en sentido contrario, yendo de menos a más si podemos acceder a las variables, pero solo en modo consulta.

Si desde f1() quisiéramos no solo consultar, sino además modificar el valor de la variable a nivel de módulo, nivel0, ya hemos visto que debemos utilizar el calificativo global, como ilustra el Ejemplo 2:

# Ejemplo 2

def f1():
    
    def f2():
        nivel2 = 2
        print(nivel0, nivel1, nivel2)
        
    nivel1 = 1
    global nivel0
    nivel0 = 5
    f2()
    print(nivel0, nivel1)

nivel0 = 0
f1()
print(nivel0)

>>> 
5 1 2
5 1
5

De igual modo, desde la función más interna, f2(), también podríamos modificar la variable de módulo nivel0 usando el calicativo global:

# Ejemplo 3

def f1():
    
    def f2():
        nivel2 = 2
        global nivel0
        nivel0 = 6
        print(nivel0, nivel1, nivel2)
        
    nivel1 = 1
    f2()
    print(nivel0, nivel1)

nivel0 = 0
f1()
print(nivel0)

>>> 
6 1 2
6 1
6

Y ahora la pregunta del millón… ¿Y si quisiéramos modificar la variable nivel1 desde dentro de f2()?

La respueta NO es global, como se ve en el Ejemplo 4:

# Ejemplo 4

def f1():
    
    def f2():
        global nivel1
        nivel1 = 7
        nivel2 = 2
        print(nivel0, nivel1, nivel2)
        
    nivel1 = 1
    f2()
    print(nivel0, nivel1)

nivel0 = 0
f1()
print(nivel0)

>>> 
0 7 2
0 1
0

Observa con cuidado este resultado: la primera línea corresponde a la impresión de f2(). Como vemos, ha impreso el valor 7, correspondiente a nivel1. Sin embargo, la siguiente línea, que corresponde a la impresión propia de f1(), un instante después, nos demuestra que el valor de nivel1 no ha sido cambiado, manteniendo su valor 1 dentro de f1.

La cuestión es que el calificativo global solo puede usarse para variables globales a nivel de módulo. Para poder modificar la variable a nivel de función nivel1, necesitamos un nuevo calificador: nonlocal.

El código siguiente es exacto al anterior, pero cambiando la palabra global por nonlocal.

# Ejemplo 5

def f1():
    
    def f2():
        nonlocal nivel1
        nivel1 = 7
        nivel2 = 2
        print(nivel0, nivel1, nivel2)
        
    nivel1 = 1
    f2()
    print(nivel0, nivel1)

nivel0 = 0
f1()
print(nivel0)

Esta vez sí: hemos logrado modificar nivel1 desde dentro de f2():

>>> 
0 7 2
0 7
0

En el Ejemplo 6 modificamos, desde dentro de f2() tanto nivel0 como nivel1. Observa el uso combinado de global y nonlocal:

# Ejemplo 6

def f1():
    
    def f2():
        global nivel0
        nonlocal nivel1
        nivel0 = 8
        nivel1 = 9
        nivel2 = 2
        print(nivel0, nivel1, nivel2)
        
    nivel1 = 1
    f2()
    print(nivel0, nivel1)

nivel0 = 0
f1()
print(nivel0)

>>> 
8 9 2
8 9
8

Moraleja: utiliza global para poder modificar una variable creada a nivel de módulo desde dentro de una función definida en ese módulo, aunque esté anidada dentro de otra función; utiliza nonlocal para modificar una variable creada a nivel de función desde otra función definida dentro de aquella.

Javier Montero Gabarró


Python: El calificador de ámbito nonlocal


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

4 opiniones en “Python: El calificador de ámbito nonlocal”

  1. “Pese a la existencia del calificativo global, la asignación nivel1 = 7 dentro de f2() no ha hecho más que construir una nueva variable local de ámbito f2(). ”

    En mi versión de Python (3.3.3) no construye una nueva variable local llamada nivel1, sino que construye una nueva variable global llamada nivel1.

    La prueba es que puedo acceder a ella desde el módulo principal.
    >>> print(nivel1)
    7

    Si construyese una local me daría error.

    Un saludo y gracias por curso!!

  2. Como siempre bien explicado y sencillo. Ya me ayudaste tremendamente con latex, y ahora que estoy pasando de código Fortran a python vuelvo a hacer uso de tu blog. Muchas gracias.

Deja un comentario