El alcance de una variable determina en qué partes del programa puede utilizarse un nombre y qué valor encuentra Python cuando existen nombres iguales en diferentes niveles. Comprender esta regla evita errores como NameError, cambios inesperados en variables globales y cierres que recuerdan valores distintos de los esperados. Python resuelve los nombres siguiendo la regla LEGB: Local, Enclosing, Global y Built-in.
Este tema aparece constantemente al crear funciones en Python, escribir decoradores, utilizar funciones lambda y diseñar funciones flexibles con *args y **kwargs.
Qué es un espacio de nombres
Un espacio de nombres relaciona identificadores con objetos. El módulo tiene un espacio global, cada llamada de función crea un espacio local y Python mantiene otro espacio con nombres integrados como len, print y sum. Cuando escribes un nombre, el intérprete busca dónde está definido.
La regla LEGB
Python busca en este orden:
- Local: nombres definidos dentro de la función actual.
- Enclosing: nombres de funciones externas que contienen a la función actual.
- Global: nombres definidos en el módulo.
- Built-in: nombres integrados del lenguaje.
La búsqueda termina en el primer nivel donde encuentra el nombre.
Alcance local
def mostrar_precio():
precio = 50
print(precio)
mostrar_precio()precio existe durante la llamada a mostrar_precio(). Fuera de la función no está disponible:
print(precio) # NameErrorCada llamada crea un nuevo espacio local.
Parámetros como variables locales
def calcular_total(precio, cantidad):
subtotal = precio * cantidad
return subtotalprecio, cantidad y subtotal son locales. Los argumentos entregados pueden referirse a objetos creados fuera, pero los nombres de los parámetros pertenecen a la función.
Alcance global
impuesto = 0.21
def calcular(precio):
return precio * (1 + impuesto)La función no define impuesto, por lo que Python lo encuentra en el módulo. Leer una variable global es posible sin ninguna declaración especial.
Asignar crea un nombre local
Si una función asigna un nombre, Python normalmente lo considera local en toda la función:
contador = 0
def incrementar():
contador = contador + 1
return contadorEste código produce UnboundLocalError. Como existe una asignación a contador, Python trata el nombre como local, pero intenta leerlo antes de que tenga un valor local.
Usar global
contador = 0
def incrementar():
global contador
contador += 1
return contadorglobal indica que las asignaciones deben afectar al nombre del módulo. Funciona, pero crea estado compartido difícil de probar y mantener. Para la mayoría de aplicaciones es preferible devolver un valor, usar una clase o encapsular el estado.
def incrementar(contador):
return contador + 1
contador = incrementar(contador)Alcance enclosing
Una función interna puede leer variables de la función externa:
def crear_saludo(prefijo):
def saludar(nombre):
return f"{prefijo}, {nombre}"
return saludar
hola = crear_saludo("Hola")
print(hola("Ana"))La función saludar conserva acceso a prefijo incluso después de que crear_saludo termina. Este comportamiento se llama cierre o closure.
Modificar una variable externa con nonlocal
def crear_contador():
valor = 0
def incrementar():
nonlocal valor
valor += 1
return valor
return incrementar
contador = crear_contador()
print(contador())
print(contador())nonlocal busca el nombre en una función contenedora, no en el módulo global. Si no existe una vinculación externa válida, se produce SyntaxError.
Objetos mutables y alcance
No necesitas global para modificar el contenido de un objeto global, porque no reasignas el nombre:
registros = []
def agregar(registro):
registros.append(registro)Aunque sea válido, sigue existiendo estado compartido. El comportamiento suele ser más claro cuando la lista se pasa como argumento:
def agregar(registros, registro):
registros.append(registro)Sombrear nombres
Una variable local puede ocultar un nombre global:
estado = "global"
def ejemplo():
estado = "local"
print(estado)
ejemplo()Dentro de la función se imprime local; fuera continúa existiendo global. Este sombreado es legal, pero puede confundir si el nombre representa conceptos distintos.
No sobrescribas nombres integrados
list = [1, 2, 3]
# Ahora list ya no se refiere al tipo integrado
# nueva = list("abc") # TypeErrorEvita nombres como list, str, sum, id o input. Puedes recuperar el nombre eliminando la variable, pero es mejor elegir un identificador descriptivo desde el inicio.
Comprensiones y su alcance
En Python 3, la variable de una comprensión no se filtra al alcance exterior:
cuadrados = [numero ** 2 for numero in range(5)]
# print(numero) # NameErrorEsto evita sobrescribir accidentalmente un nombre existente.
El problema de enlace tardío en cierres
Este patrón sorprende a muchos desarrolladores:
funciones = []
for numero in range(3):
funciones.append(lambda: numero)
print([funcion() for funcion in funciones])Las lambdas buscan numero cuando se ejecutan, después de terminar el bucle, por lo que todas suelen devolver el último valor. Puedes capturarlo mediante un argumento predeterminado:
funciones = []
for numero in range(3):
funciones.append(lambda numero=numero: numero)Cada función guarda el valor de esa iteración.
Variables de módulo e importaciones
Cada módulo tiene su propio espacio global. Una variable global no es universal para todo el programa; es global dentro de su módulo. Importar con from modulo import nombre crea una vinculación en el módulo importador. Para configuraciones compartidas, evita depender de reasignaciones implícitas y define una interfaz clara.
Inspeccionar espacios de nombres
locals() devuelve una representación del espacio local y globals() del global:
configuracion = "produccion"
def inspeccionar(valor):
temporal = valor * 2
print(locals())
print("configuracion" in globals())No modifiques el diccionario devuelto por locals() esperando que cambie variables locales de forma confiable. Utilízalo principalmente para inspección y depuración.
Diseño sin globales mutables
Las funciones más fáciles de probar reciben datos y devuelven resultados:
def calcular_descuento(precio, porcentaje):
return precio * porcentajeCuando necesitas conservar estado, una clase suele ser explícita:
class Contador:
def __init__(self):
self.valor = 0
def incrementar(self):
self.valor += 1
return self.valorAsí el estado pertenece a una instancia y no a todo el módulo.
Diagnosticar UnboundLocalError
Cuando aparece UnboundLocalError, busca una asignación al mismo nombre dentro de la función. Decide si realmente querías una variable local, un parámetro, un retorno, nonlocal o, en casos excepcionales, global. No añadas global automáticamente sin entender el diseño.
La sección de nombres y vinculaciones del modelo de ejecución describe las reglas formales de alcance. El tutorial oficial sobre scopes y namespaces ofrece ejemplos de global y nonlocal.
Errores frecuentes
Los problemas habituales son reasignar una global sin declararla, abusar de global, confundir mutación con reasignación, ocultar funciones integradas y crear lambdas en un bucle sin capturar el valor. También es frecuente creer que una variable global pertenece a todos los módulos o que una variable local persiste entre llamadas.
Conclusión
La regla LEGB explica cómo Python resuelve cada nombre: primero local, luego funciones contenedoras, después el módulo y finalmente los nombres integrados. Entender la diferencia entre leer, reasignar y modificar objetos permite evitar NameError y UnboundLocalError. Siempre que sea posible, pasa datos como argumentos, devuelve resultados y encapsula el estado en objetos en lugar de depender de globales mutables.






