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.

Python: Vida y muerte de una variable

Objetivo: entender el concepto de ámbito de una variable y la clasificación de estas en globales y locales.

En un principio el mundo era sencillo y nuestros programas simples:

# Ejemplo 1

a = 1
b = 2

print(a)
print(b)

>>> 
1
2

Nuestras variables nacían, correteaban a sus anchas por el programa y cambiaban de valor según el guión establecido.

Debemos decir que, en Python, al contrario de lo que sucede en otros lenguajes, las variables no se declaran. Se crean justo en el momento en el que reciben por asignación un valor por primera vez y, a partir de ese momento, ya pueden formar parte de una expresión. Se produce un error si una variable es utilizada antes de que realice esa asignación inicial.

Hay que matizar, además, que cuando empleamos el término variable estamos refiriéndonos en realidad a su nombre. En Python es importante diferenciar el nombre de una variable del objeto real al que referencia. Sabemos que una simple asignación, como a = 1, se ocupa de crear el objeto 1, si no existe ya, y de etiquetarlo bajo el nombre a. Es más, por cuestiones de economía, un mismo objeto puede estar etiquetado con varios nombres de variable, como muestra el ejemplo 2:

Ejemplo 2

>>> a = 1
>>> b = 1
>>> a is b
True

Las variables a y b referencian al mismo objeto. Si ahora cualquiera de ellas cambiara con una nueva asignación, este vínculo común, naturalmente, desaparecería y cada nombre etiquetaria a su respectivo objeto valor.

En este escenario de plácida convivencia introdujimos un nuevo nivel de complejidad y aparecieron las funciones, como cajas negras dispuestas a facilitarnos la reutilización del código.

# Ejemplo 3

def cajanegra():
    c = 3
    print(c)

a = 1
b = 2

print(a)
print(b)
cajanegra()

>>> 
1
2
3

Y desde ese momento empezó la jerarquización. Como sucede en nuestro mundo, en el que lo fácil o difícil que será la vida depende bastante del lugar de nacimiento, lo mismo ocurre con los nombres de variables. A las variables que residen fuera de toda función, como a y b en el ejemplo, se las demomina globales, mientras aquellas que son creadas en las funciones reciben el nombre de locales.

Las variables locales, al contrario que las globales, tienen una vida efímera. Solo existen durante el momento en el que es llamada la función. En el momento en el que esta concluye, desaparecen, pasando por el programa sin pena ni gloria.

Además, solo son visibles dentro de la función; para el resto del código son completamente inexistentes. En el ejemplo anterior, tratar de usar la variable c desde el cuerpo del programa principal ocasionaría un error.

La zona del programa en la que una variable puede ser utilizada es lo que se conoce como su ámbito. Decimos que el ámbito de una variable local está limitado al código de la función en la que está definida.

Para ilustrar este concepto con claridad, vamos a construir un escenario en el que supuestamente hay un conflicto de nombres entre una variable global y otra local:

# Ejemplo 4

def cajanegra():
    c = 3
    print('La variable c dentro de la función tiene por valor', c)

a = 1
b = 2
c = 5

print(a)
print(b)
cajanegra()
print('La variable c fuera de la función tiene por valor', c)

>>> 
1
2
La variable c dentro de la función tiene por valor 3
La variable c fuera de la función tiene por valor 5

Observa que no existe tal conflicto. No tiene nada que ver la variable c de dentro de la función a la variable c de fuera, a pesar de tener el mismo nombre. Residen en espacios de nombres diferentes. Pese al hecho de haber asignado el valor 3 a la variable dentro de la función, su valor fuera de ella no se ha visto afectado en absoluto.

Las variables locales no son solo las que están dentro del cuerpo de una función. También lo son los argumentos formales empleados en su declaración.

En el ejemplo 5, las variables x, y y c son todas locales:

# Ejemplo 5

def cajanegra(x, y):
    c = x + y
    print(c)

a = 1
b = 2
cajanegra(a, b)

>>> 
3

En el momento en el la función es invocada, x e y reciben su asignación; en este caso, el valor de las variables a y b, respectivamente.

Las variables globales tienen la particularidad de que también son visibles dentro de la función:

# Ejemplo 6

def cajanegra():
    c = 3
    print(a)
    print(b)
    print(c)

a = 1
b = 2
cajanegra()

>>> 
1
2
3

Sin embargo, todo intento de modificar una variable global desde dentro de una función mediante una nueva asignación fracasa, como podemos comprobar en el ejemplo 7:

# Ejemplo 7

def cajanegra():
    c = 3
    print(a)
    b = 5
    print('La variable b dentro de la función vale', b)
    print(c)

a = 1
b = 2
cajanegra()
print('La variable b fuera de la función sigue valiendo', b)

>>> 
1
La variable b dentro de la función vale 5
3
La variable b fuera de la función sigue valiendo 2

Lo importante a comprender es que, desde el preciso momento en el que una variable recibe una asignación dentro de una función, pasa a ser calificada como local. Cuando eso ocurre, la variable tiene una visibilidad limitada al cuerpo de la función y deja de ser considerada como global si ya existía el nombre en el ámbito exterior. Por eso, en el ejemplo, la variable a es global, mientras que b y c son locales.

Dentro de una función, la misma variable no puede ser en unos momentos global y en otros local. Si hay una asignación, aunque sea posterior a su uso como variable global, la variable será considerada local y se producirá un error:

# Ejemplo 8

def cajanegra():
    c = 3
    print(b)
    b = 5

a = 1
b = 2
cajanegra()

>>> 
Traceback (most recent call last):
  File "C:\Users\Javier\Dropbox\pythonprad\pruebas\probando.py", line 45, in <module>
    cajanegra()
  File "C:\Users\Javier\Dropbox\pythonprad\pruebas\probando.py", line 40, in cajanegra
    print(b)
UnboundLocalError: local variable 'b' referenced before assignment

El mensaje es concluyente: la variable b ha sido referenciada antes de haber recibido una asignación, algo prohibido en Python. Si no hubiese existido la asignación en la tercera línea del cuerpo de cajanegra(), b hubiese sido considerada una variable global y el codigo sería válido. Sin embargo, la asignación hace que b se trate como local, invalidando su rol global.

En numerosas ocasiones puede resultar conveniente no solo poder acceder a una variable global desde dentro de una función, sino poder cambiar su valor mediante una nueva asignación. Para lograr esto, hay que calificar la variable externa dentro de la función empleando la palabra global.

# Ejemplo 9

def cajanegra():
    c = 3
    global b
    b = 5
    print('Dentro de la función b vale', b)

a = 1
b = 2
cajanegra()
print('Fuera de la función b también vale', b)

>>> 
Dentro de la función b vale 5
Fuera de la función b también vale 5

Al calificar b como global dentro de la función estamos indicando que las asignaciones posteriores de esa variable se realizarán en un ámbito global en vez de local, modificando así el valor externo.

Hay que usar las variables globales con precaución. Permitir a las funciones que modifiquen nuestras variables externas es una práctica que puede dificultar la localización de errores cuando las cosas no funcionan como debieran. Sin embargo, son muy útiles para almacenar información de estado que luego podrá recuperarse al invocar nuevamente la función u otra diferente. En el ejemplo siguiente, utilizamos la variable global suma para retener el efecto de cada invocación a la función:

# Ejemplo 10

def sumar5():
    global suma
    suma = suma + 5
    print('La nueva suma es', suma)

suma = 0
sumar5()
sumar5()
sumar5()

>>> 
La nueva suma es 5
La nueva suma es 10
La nueva suma es 15

Cada invocación a sumar5() agrega cinco al valor de suma. La variable suma ha sido declarada como global dentro de la función para poder actualizar el valor externo, que será utilizado nuevamente al volver a llamar a la función.

Existe otro calificativo, además de global, con el que podemos etiquetar las variables dentro de una función. En el siguiente artículo lo presentaremos en sociedad.

Javier Montero Gabarró


Python: Vida y muerte de una variable


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.

Uso de cookies

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información.plugin cookies

ACEPTAR
Aviso de cookies