Alcance de variables en Python y la regla LEGB
Respuesta rápida: Python busca cada nombre en este orden: Local, Enclosing, Global y Built-in, de donde surge LEGB. Una asignación dentro de una función suele crear un nombre local. Prefiere parámetros y valores de retorno; usa global para estado intencional del módulo y nonlocal para modificar el estado de una función externa.
Esta regla explica la mayoría de los errores NameError y UnboundLocalError. El alcance no indica solamente dónde “existe” una variable: determina qué vínculo entre un nombre y un objeto empleará cada instrucción. Si necesitas repasar la base, consulta la guía de funciones en Python.
Nombres, objetos y alcances
En Python, una variable es un nombre enlazado a un objeto. cantidad = 3 crea o reemplaza el vínculo de cantidad en el espacio de nombres actual. Varios nombres pueden apuntar al mismo objeto, y reasignar uno no modifica necesariamente dicho objeto.
Un namespace relaciona nombres con objetos; el alcance es la región de código desde la que se consulta directamente un namespace. Módulos, funciones y clases crean espacios de nombres, aunque su resolución presenta diferencias. La documentación oficial sobre alcances y namespaces detalla este modelo.
tasa = 0.10
def calcular_total(precio):
descuento = 5
return precio * (1 + tasa) - descuento
print(calcular_total(100)) # 105.0
Durante la llamada, precio y descuento son locales; tasa pertenece al módulo. Leerla no requiere global. Definir con claridad la propiedad de cada nombre ayuda al dividir proyectos en módulos y paquetes.
Cómo funciona la búsqueda LEGB
Python recorre cuatro niveles:
- Local (L): parámetros y nombres definidos en la función actual.
- Enclosing (E): alcances de las funciones externas que rodean la actual.
- Global (G): namespace del módulo donde se definió la función.
- Built-in (B): nombres de
builtins, comolen,sumyprint.
etiqueta = "global"
def externa():
etiqueta = "enclosing"
def interna():
etiqueta = "local"
return etiqueta
return interna()
print(externa()) # local
Si eliminas la asignación de interna, obtienes enclosing. Si también eliminas la de externa, la búsqueda encuentra el valor global. Cuando ningún nivel contiene el nombre, se produce NameError. El modelo de ejecución de Python especifica estas reglas.
Evita ocultar nombres built-in:
## Evítalo: list deja de ser el constructor incorporado.
list = [1, 2, 3]
## Mejor: un nombre descriptivo.
numeros = [1, 2, 3]
Lo mismo se aplica a str, id, filter e input. Aunque la asignación es válida, una llamada posterior puede fallar y el lector debe recordar un significado inesperado.
Alcance local y UnboundLocalError
Python clasifica los nombres locales al compilar todo el cuerpo de una función, no línea por línea. Si existe una asignación a un nombre en cualquier lugar de la función, ese nombre es local salvo que se declare global o nonlocal.
contador = 10
def incrementar():
print(contador)
contador += 1
incrementar() # UnboundLocalError
contador += 1 lee y asigna, por lo que contador es local. El print intenta leer el vínculo local antes de que exista. Un diseño explícito suele ser preferible:
def incrementar(contador):
return contador + 1
contador = incrementar(contador)
Los parámetros hacen visibles las dependencias y facilitan las pruebas. Es una ventaja tanto en código funcional como en programación orientada a objetos.
Cuándo usar global
global dirige las asignaciones de una función al namespace del módulo. La declaración afecta a todo el bloque y debe preceder al uso de ese nombre dentro del mismo bloque.
llamadas = 0
def registrar_llamada():
global llamadas
llamadas += 1
Puede ser razonable para una configuración pequeña o instrumentación controlada. Sin embargo, el estado global mutable acopla el resultado al orden de llamadas y complica las pruebas aisladas. Devolver el nuevo valor, guardar estado en un objeto o inyectar una dependencia suele ser más claro.
global no crea una variable universal entre todos los módulos. Se refiere al namespace global del módulo que contiene la función. Consulta la referencia oficial de global para conocer su comportamiento exacto.
Closures y nonlocal
Una función anidada puede conservar vínculos de su función externa después de que esta termine. Es una closure, patrón desarrollado en la guía de closures en Python.
def crear_contador(inicial=0):
valor = inicial
def siguiente():
nonlocal valor
valor += 1
return valor
return siguiente
contador = crear_contador(10)
print(contador()) # 11
print(contador()) # 12
Sin nonlocal, la asignación convertiría valor en local para siguiente, y += provocaría UnboundLocalError. nonlocal busca en funciones externas, no en el módulo, y exige que ya exista un vínculo.
Las closures sirven para estados privados pequeños, fábricas de funciones y decoradores. La guía de decoradores en Python muestra una aplicación habitual. Si el estado posee muchas operaciones o invariantes, una clase suele expresar mejor el diseño.
Clases, comprehensions y límites sutiles
El cuerpo de una clase tiene namespace propio, pero un método no lo considera un alcance enclosing. Accede a atributos mediante self, la clase o un método de clase:
class Carrito:
limite = 20
def acepta(self, cantidad):
return cantidad <= self.limite
Escribir solamente limite dentro del método no encuentra automáticamente Carrito.limite. LEGB comienza en el alcance local del método y continúa hacia el módulo donde se definió.
Las comprehensions de Python 3 también poseen alcance propio. Su variable de iteración no se filtra:
cuadrados = [numero ** 2 for numero in range(4)]
## numero no está definido aquí
Las closures creadas en bucles requieren cuidado por el enlace tardío: consultan el nombre cuando se ejecutan.
funciones = [lambda n=n: n * 2 for n in range(3)]
print([funcion() for funcion in funciones]) # [0, 2, 4]
El argumento predeterminado captura el valor de cada iteración. Sin n=n, todas observarían el último valor. La guía de lambda, map y filter ofrece más contexto práctico.
Errores comunes y diagnóstico
Confundir mutación con asignación: una función puede mutar una lista global sin global porque no reasigna el nombre. Sigue siendo un efecto secundario que debe ser intencional.
elementos = []
def agregar(elemento):
elementos.append(elemento)
Usar global ante cualquier error: puede ocultar la excepción sin solucionar el acoplamiento. Considera primero un parámetro, un retorno o un atributo.
Esperar alcance de bloque: if, for, while, with y try no crean un alcance local separado. Sus asignaciones quedan en la función o el módulo actual, aunque una rama no ejecutada podría no crear el nombre.
Depurar por intuición: locals() y globals() permiten inspeccionar namespaces y son útiles para aprender o diagnosticar. No modifiques sus resultados como diseño normal; expresa el flujo de datos directamente.
Lista de comprobación final
- Localiza la función o el módulo donde se asigna cada nombre.
- Aplica LEGB y detente en el primer vínculo existente.
- Prefiere parámetros y retornos al estado global mutable.
- Reserva
nonlocalpara estado sencillo de una closure. - Accede a atributos de clase mediante
selfo la clase. - No ocultes built-ins como
listostr. - Ante
UnboundLocalError, busca una asignación posterior en la función.
LEGB resulta predecible cuando separas nombres, objetos y namespaces. Expón las dependencias en las firmas y trata global y nonlocal como herramientas específicas, no como atajos. Obtendrás código más fácil de leer, probar y modificar.