Cómo usar variables globales y no locales en Python

En este tutorial, veremos cómo usar variables globales y no locales en las funciones de Python. Cubriremos las buenas prácticas y qué buscar, con ejemplos.

Introducción

En este artículo, veremos Variables globales y no locales en Python y cómo usarlas para evitar problemas al escribir código.

Comenzaremos con una breve introducción a los ámbitos de las variables antes de comenzar con el cómo y el por qué del uso de variables globales y no locales en sus propias funciones.

Ámbitos en Python

Antes de que podamos comenzar, primero tenemos que tocar los ámbitos. Para aquellos de ustedes que están menos familiarizados, "alcance" se refiere al contexto en el que se define una variable y cómo se puede acceder o modificar o, más específicamente, desde dónde se puede acceder.

Y en la programación, como en la vida, el contexto es importante.

Al hacer referencia a Python en este momento, puede inferir del contexto que me estoy refiriendo al lenguaje de programación. Sin embargo, en otro contexto, Python podría ser una referencia a una serpiente o un grupo cómico.

Los ámbitos Global y local son cómo su programa entiende el contexto de la variable a la que está haciendo referencia.

Como regla, las variables definidas dentro de una función o clase (como una variable de instancia) son locales por defecto, y aquellas fuera de las funciones y clases son globales por defecto.

Variables locales en Python

Con eso entendido, veámoslo en acción. Comenzaremos definiendo una función con su propia variable local dentro. En esta función tenemos la variable fruta, que inicializamos como una lista e imprimimos:

1
2
3
4
5
def shopping_list():
    fruit = ['apple', 'banana']
    print(fruit)
    
shopping_list()

Y como era de esperar, esto funciona de maravilla:

1
['apple', 'banana']

Pero, ¿qué sucede cuando movemos la declaración de impresión fuera de la función?

1
2
3
4
5
def shopping_list():
    fruit = ['apple', 'banana']
    
shopping_list()
print(fruit)

Obtenemos un error"

1
2
3
Traceback (most recent call last):
  File "<string>", line 5, in <module>
NameError: name 'fruit' is not defined

Específicamente, un NameError, ya que la fruta se definió localmente y, por lo tanto, permanece confinada a ese contexto. Para que nuestro programa entienda la variable globalmente (fuera de la función), necesitamos definirla globalmente.

Variables globales en Python

¿Qué pasa si en lugar de definir inicialmente nuestra variable dentro de la función, la movemos fuera y la inicializamos allí?

En este caso, podemos hacer referencia fuera de la función y todo funciona.

Pero si tratamos de redefinir la variable de la fruta dentro de shopping_list, esos cambios no se actualizarán a la variable global original, sino que se aislarán localmente:

1
2
3
4
5
6
7
fruit = ['apple', 'banana']

def shopping_list():
    fruit = ['apple', 'banana', 'grapes']

shopping_list()
print(fruit)

Producción:

1
['apple', 'banana']

Esto se debe a que la fruta que hemos modificado en la función shopping_list() es una nueva variable local. Lo creamos, le asignamos un valor y no hicimos nada después de eso. Es efectivamente un código completamente redundante. La sentencia print() imprime el valor de la variable global que está dentro de su alcance.

La palabra clave global

Si queremos que esos cambios se reflejen en nuestra variable global, en lugar de hacer una nueva local, todo lo que tenemos que hacer es agregar la palabra clave global. Esto nos permite comunicar que la variable fruta es de hecho una variable global:

1
2
3
4
5
6
7
8
fruit = ['pineapple', 'grapes']

def shopping_list():
    global fruit
    fruit = ['pineapple', 'grapes', 'apple', 'banana']

shopping_list()
print(fruit)

Y efectivamente, la variable global se modifica con los nuevos valores, por lo que llamamos imprimir (fruta), los nuevos valores se imprimen:

1
['pineapple', 'grapes', 'apple', 'banana']

Al definir el contexto de la variable fruit a la que nos referimos como global, podemos redefinirla y modificarla a nuestro gusto sabiendo que los cambios que hagamos dentro de la función se mantendrán.

También podríamos definir una variable global dentro de nuestra función y hacer que se pueda hacer referencia a ella y acceder a ella desde cualquier otro lugar.

1
2
3
4
5
6
7
def shopping_list():
    global fruit
    fruit = ['pineapple', 'grapes', 'apple', 'banana']


shopping_list()
print(fruit)

Esto daría como resultado:

1
['pineapple', 'grapes', 'apple', 'banana']

Incluso podríamos declarar una variable global dentro de una función y acceder a ella en otra sin especificarla como global en la segunda:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def shopping_list():
    global fruit
    fruit = ['pineapple', 'grapes', 'apple', 'banana']

def print_list():
    print(fruit)
    
shopping_list()
print(fruit)
print_list()

Esto resulta en:

1
2
['pineapple', 'grapes', 'apple', 'banana']
['pineapple', 'grapes', 'apple', 'banana']

Precaución al usar variables globales

Si bien poder modificar una variable global localmente es una pequeña herramienta útil, debe tratarla con bastante precaución. La reescritura excesiva y la anulación del alcance es una receta para el desastre que termina con errores y comportamientos inesperados.

Siempre es importante asegurarse de que está manipulando una variable solo en el contexto que la necesita y, de lo contrario, dejarla en paz, este es el impulso principal detrás del principio de encapsulación.

Echaremos un vistazo rápido a un ejemplo de un problema potencial antes de pasar a algunas de las formas en que las variables globales pueden ser útiles en su propio código:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fruit = ['pineapple', 'grapes', 'apple', 'banana']

def first_item():
    global fruit
    fruit = fruit[0]
    
def iterate():
    global fruit
    for entry in fruit:
        print(entry)
    
iterate()
print(fruit)
first_item()
print(fruit)

Ejecutando el código anterior, obtenemos el siguiente resultado:

1
2
3
4
5
6
pineapple
grapes
apple
banana
['pineapple', 'grapes', 'apple', 'banana']
pineapple

En este ejemplo, hacemos referencia a la variable en ambas funciones, first_item() e iterate(). Todo parece funcionar bien si llamamos a iterate() y luego first_item().

Si invertimos ese orden o intentamos iterar después, nos encontramos con un gran problema:

1
2
3
4
first_item()
print(fruit)
iterate()
print(fruit)

Esto ahora da como resultado:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
pineapple
p
i
n
e
a
p
p
l
e
pineapple

Es decir, fruta ahora es una cadena que se repetirá. Lo que es peor es que este error no se presentará hasta que presumiblemente sea demasiado tarde. El primer código funcionó aparentemente bien.

Ahora, este problema es obvio a propósito. Manipulamos una variable global directamente: he aquí que ha cambiado. Sin embargo, en estructuras más complejas, uno puede accidentalmente llevar la modificación de la variable global demasiado lejos y obtener resultados inesperados.

La palabra clave no local

El hecho de que deba ser cauteloso no significa que las variables globales no sean también increíblemente útiles. Las variables globales pueden ser útiles siempre que desee actualizar una variable sin proporcionarla en la declaración de devolución, como un contador. También son muy útiles con funciones anidadas.

Para aquellos de ustedes que usan Python 3+, pueden usar nonlocal, una palabra clave que funciona de manera muy similar a global, pero principalmente tiene efecto cuando está anidada en métodos. nonlocal esencialmente forma un punto intermedio entre el alcance global y local.

Ya que hemos estado usando listas de compras y frutas para la mayoría de nuestros ejemplos, podríamos pensar en una función de pago que suma el total de las compras:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def shopping_bill(promo=False):
    items_prices = [10, 5, 20, 2, 8]
    pct_off = 0

    def half_off():
        nonlocal pct_off
        pct_off = .50

    if promo:
        half_off()

    total = sum(items_prices) - (sum(items_prices) * pct_off)
    print(total)
    
shopping_bill(True)

Ejecutando el código anterior, obtenemos el resultado:

1
22.5

De esta manera, la variable de conteo global sigue siendo local para la función externa y no afectará (o existirá) en un nivel superior. Esto le da cierta libertad para agregar modificadores a sus funciones.

Siempre puede confirmar esto intentando imprimir pct_off fuera del método de factura de compra:

1
NameError: name 'pct_off' is not defined

Si hubiéramos usado la palabra clave global en lugar de la palabra clave nonlocal, imprimir pct_off daría como resultado:

1
0.5

Conclusión

Al final del día, las palabras clave globales (y no locales) son una herramienta y, cuando se usan correctamente, pueden abrir muchas posibilidades para su código. Yo personalmente uso estas dos palabras clave con bastante frecuencia en mi propio código, y con suficiente práctica llegarás a ver cuán poderosas y útiles realmente pueden ser.

Como siempre, ¡muchas gracias por leer y Happy Hacking!

Licensed under CC BY-NC-SA 4.0