El aeropuerto de las etiquetas: el modelo de objetos de Python
·
Resumen del curso
1
La etiqueta no es la maleta: nombres y objetos
2
Maletas selladas: mutable e inmutable
3
Maletas dentro de maletas: contenedores y copias
4
Al entregar una maleta: funciones y argumentos
5
Zonas y pases: ámbitos
6
Equipaje de mano: clausuras
7
La especificación de la maleta: clases e instancias
8
Procedimientos estándar del aeropuerto: métodos dunder
9
La especificación de la especificación: herencia y MRO
10
El control: el secreto del acceso a atributos
11
Maletas sin dueño: memoria y GC
12
Números de taquilla: hash, igualdad y repaso final
Tiempo 3:20–4:10
Parte de equipaje perdido
LOST-005
Lo reporta
Han, del equipo de operaciones del control de seguridad
Qué se perdió
Un contador de maletas inspeccionadas. Arriba del todo está claramente scanned = 0, pero en cuanto se llama a scan() salta un UnboundLocalError.
Para cerrar el caso
Explicar por qué la función no vio el nombre de fuera, y arreglar el contador para que cuente bien.
Aeropuerto Internacional Haneulseom · Centro de equipajes
La supervisora Do Miran señala con el dedo el plano del aeropuerto colgado en la pared. «Puerta de embarque, terminal, aeropuerto, país. Los pases se comprueban de dentro hacia fuera. Si está dentro, ni siquiera se mira fuera.»
El parte de hoy es el contador del control de seguridad. Pusieron scanned = 0 fuera e intentaron sumarle uno dentro de la función, pero ya en la primera línea salta un error que dice que ese valor no existe. La supervisora señala la última línea de la función. «La culpable es esta. La línea que asigna un valor.»
Al leer un nombre, Python lo busca en cuatro zonas, por orden, de dentro hacia fuera. Se detiene en la primera donde lo encuentra.
| Orden | Zona | Qué es | En el aeropuerto sería |
|---|---|---|---|
| L | Local | Dentro de la función que se está ejecutando | La puerta de embarque |
| E | Envolvente (Enclosing) | Las funciones exteriores que envuelven a esa función | La terminal |
| G | Global | El módulo donde se definió la función | El aeropuerto |
| B | Integrado (Built-in) | Nombres básicos como len, print o list | El país |
zone = "G"
def terminal():
gate = "E"
def boarding():
seat = "L"
print(seat, gate, zone, len("abc"))
boarding()
terminal()
# L E G 3seat se encuentra dentro de boarding (L), y gate, en la función envolvente terminal (E).zone se encuentra en el módulo (G), y len, entre los integrados (B).NameError.boarding desde otro sitio, gate se busca en terminal.El mapa de ámbitos de abajo no ejecuta el código: solo lo lee y muestra en qué zona se encuentra cada nombre. La última función, broken, es la protagonista del siguiente apartado.
Mapa de ámbitos
1
zone = "G"
2
3
def terminal():
4
gate = "E"
5
def boarding():
6
seat = "L"
7
print(seat, gate, zone, len("abc"))
8
boarding()
9
10
def broken():
11
print(zone)
12
zone = "L"
Nombre
seat @ terminal. seat es un nombre local de la función interior boarding, así que desde la exterior, terminal, no se ve. La búsqueda va siempre de dentro hacia fuera.zone @ broken aparece como «Encontrado aquí» en L. Eso significa que el nombre quedó fijado como local de esa función, no que tenga un valor.Este es el código del parte:
scanned = 0
def scan(bag):
print(bag, scanned)
scanned = scanned + 1
scan("T1")
# UnboundLocalError: cannot access local variable 'scanned' where it is not associated with a valueEl error no salta en la línea 5, sino en la línea 4, el print. La línea 5 ni siquiera se ha ejecutado todavía. Aun así, Python no busca fuera el scanned de la línea 4.
La regla es una sola: si en cualquier parte de la función se asigna un valor a un nombre, ese nombre es local en toda la función. Esta decisión no se toma al ejecutar, sino al compilar la función.
def scan(bag):
print(bag, scanned)
scanned = scanned + 1
print(scan.__code__.co_varnames)
# ('bag', 'scanned')
print(issubclass(UnboundLocalError, NameError))
# Trueco_varnames es la lista de nombres locales que fijó el compilador. scanned ya está en ella.scanned», y esa etiqueta todavía no cuelga de ninguna maleta. Es exactamente lo que dice el mensaje de error.UnboundLocalError es un tipo de NameError.Las líneas que no se ejecutan cuentan igual:
x = 10
def f():
print(x)
if False:
x = 1
f()
# UnboundLocalError: cannot access local variable 'x' where it is not associated with a value«Asignar un valor» no es solo =. +=, for x in ..., import x, def x():, class x:, with ... as x, except ... as x y del x también crean un nombre local en esa función.
Compara las tres versiones en el tablero de abajo. La primera es el código del parte, y en el último paso aparece el error.
Tablero de etiquetas
Paso 1 / 0
Lo siguiente que se ejecuta: 0
Preparando el laboratorio…
Acaba de cambiar
Maleta sellada (inmutable)
Pin dice
→ Cuando veas un UnboundLocalError, mira por debajo de la línea del error. En algún punto de la misma función se le estará asignando un valor a ese nombre.
global y nonlocal (7 min)Si quieres asignar un valor a un nombre de una zona exterior, decláralo antes.
scanned = 0
def scan(bag):
global scanned
scanned = scanned + 1
scan("T1")
scan("T2")
print(scanned)
# 2def terminal():
boarded = 0
def board():
nonlocal boarded
boarded += 1
board()
board()
return boarded
print(terminal())
# 2global x: en esta función, x es el nombre del módulo. Al asignarle un valor se mueve la etiqueta del módulo.nonlocal x: en esta función, x es el nombre de la función envolvente más cercana. No sirve para nombres del módulo.nonlocal no existe en ninguna función envolvente, antes de ejecutar salta SyntaxError: no binding for nonlocal 'x' found.global x, obtienes SyntaxError: name 'x' is assigned to before global declaration.Para solo leer no hace falta declarar nada. El boarding de 5-1 leía gate y zone sin declaraciones. Solo hay que declarar cuando se asigna un valor.
global cambia a escondidas el estado de fuera de la función, lo que hace que la función sea difícil de probar por separado. Casi siempre es mejor recibir el valor como argumento y devolver el nuevo. nonlocal volverá a aparecer con las clausuras de la lección 6.
En Python crean una zona nueva las funciones (def, lambda), las comprensiones y el cuerpo de las clases. Los bloques if, for, while, with y try no crean ninguna.
for i in range(3):
pass
print(i)
# 2
if True:
inside = "if"
print(inside)
# if
sq = [n * n for n in range(3)]
print(sq)
# [0, 1, 4]
print(n)
# NameError: name 'n' is not defined| Crean zona | No crean zona |
|---|---|
def, lambda, comprensiones de listas, conjuntos y diccionarios, expresiones generadoras, cuerpo de class | if, for, while, with, try |
for termine, la variable del bucle i se queda con el último valor (2). Lo mismo pasa con los nombres creados dentro del bucle.n de la comprensión no se escapa hacia fuera. Desde 3.12, CPython ya no convierte las comprensiones en una función aparte, sino que las ejecuta en línea (PEP 709), pero la regla de que el nombre no se escapa sigue igual.Los nombres integrados están en la zona más exterior (B). Por eso, si creas un nombre igual en el ámbito global, a partir de ese momento se encuentra primero el nombre global.
list = ["T1", "T2"]
print(list("ab"))
# TypeError: 'list' object is not callableLa etiqueta list quedó colgada en el ámbito global de una maleta lista, así que list("ab") intenta llamar a esa lista y falla. El list integrado no ha desaparecido: solo está tapado.
list = ["T1", "T2"]
del list
print(list("ab"))
# ['a', 'b']
import builtins
print(builtins.list is list, builtins.len("abc"))
# True 3list, vuelve a verse el list integrado.builtins. Aunque estén tapados, siempre puedes sacarlos con builtins.list.shout = print haces del print, obtienes NameError: name 'print' is not defined. En el ámbito global no hay ninguna etiqueta print, así que no hay nada que quitar.No uses como variables nombres como list, dict, str, id, type, input, max o sum. En ese archivo taparías las funciones integradas.
locals(), globals(), vars(), dir() (4 min)globals(): el diccionario de la zona del módulo. Llamado desde el módulo, es el propio almacén de verdad.locals(): los nombres de la zona actual. En el módulo es lo mismo que globals().vars(obj): el __dict__ del objeto. Sin argumentos es lo mismo que locals() (lección 7).dir(): devuelve solo la lista de nombres, ordenada.Desde 3.13, el significado de locals() dentro de una función está bien definido (PEP 667). Cada llamada devuelve en un diccionario nuevo una instantánea de ese momento. Modificar ese diccionario no cambia las variables locales.
def check():
y = 1
locals()["y"] = 99
print(y, locals()["y"])
snap = locals()
y = 2
print(snap["y"], locals()["y"])
check()
# 1 1
# 1 2
def where(a):
b = 2
return dir(), vars()
print(where(1))
# (['a', 'b'], {'a': 1, 'b': 2})locals()["y"] = 99 solo modifica la instantánea recién creada. La variable y sigue valiendo 1.snap conserva el valor del momento en que se tomó (1). Solo en un nuevo locals() se ve el 2.locals().Hay dos formas de arreglar el contador: mover directamente la etiqueta de fuera (global) o recibir el valor y devolver el nuevo.
scanned = 0
def scan(bag):
global scanned
print(bag, scanned)
scanned = scanned + 1
scan("T1")
scan("T2")
print(scanned)
# T1 0
# T2 1
# 2
def scan_count(bag, count):
print(bag, count)
return count + 1
total = 0
total = scan_count("T1", total)
total = scan_count("T2", total)
print(total)
# T1 0
# T2 1
# 2Han elige la segunda forma: aunque el número de controles crezca, cada uno puede llevar su propia cuenta. La supervisora añade: «Cada zona tiene sus propias etiquetas. Si quieres mover una etiqueta de otra zona, primero enseña el pase.»
Informe de cierre — LOST-005. Por el scanned = ... de la parte de abajo de la función, scanned pasó a ser un nombre local en toda la función, y la primera línea lanzó UnboundLocalError. Resuelto recibiendo el valor como argumento y devolviéndolo.
boarding de 5-1 añades una línea zone = "L" debajo del print? Compáralo con zone @ broken en el mapa de ámbitos.nonlocal, crea un contador que cuente haciendo append a una lista de la función exterior. ¿Por qué funciona sin declarar nada? (Pista: mover la etiqueta frente a cambiar la maleta.)sum = 0 al principio del módulo y luego llamas a sum([1, 2, 3]), ¿qué error obtienes? Arréglalo usando builtins.La libreta de la supervisora
Los nombres se buscan en el orden L → E → G → B, y la búsqueda se detiene en el primer sitio donde aparecen.
La zona no depende de dónde se llama a la función, sino de dónde está escrita.
Si en cualquier parte de la función se asigna un valor, el nombre es local en toda la función. De ahí el UnboundLocalError.
global y nonlocal solo hacen falta para mover etiquetas de fuera. Para leer, no.
Los bucles y los if no crean zona; las funciones y las comprensiones, sí.
list = … tapa el list integrado. Los nombres integrados están en builtins.
Dentro de una función, locals() es una instantánea (3.13, PEP 667). Modificarla no cambia las variables.
← Anterior
Lección 4 · Al entregar una maleta: funciones y argumentos
Siguiente →
Lección 6 · Equipaje de mano: clausuras
Recibe avisos de nuevas lecciones
Te avisaremos por correo cuando este curso tenga lecciones nuevas o se abra un curso de continuación.
R
ReflexKR
Aprende Python y Reflex haciendo. Todos los ejemplos se ejecutaron y verificaron en Reflex 0.10.0.
ReflexKR es gratis y sin banners publicitarios. Si te ayudó, puedes apoyarlo con un café. ☕ Invítanos un café
Aviso de afiliados© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
Preguntas y respuestas
Pregunta lo que quieras sobre esta lección. Aquí se reúnen las preguntas de todas las versiones de idioma de esta página.