R

ReflexKR

Documentación de Reflex

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

El aeropuerto de las etiquetas: el modelo de objetos de Python
Lección 5

Tiempo 3:20–4:10

Zonas y pases: ámbitos

Parte de equipaje perdido

LOST-005

Un nombre que está justo al lado y no se encuentra

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.»

5-1. El orden en que se buscan los nombres: LEGB (9 min)

Al leer un nombre, Python lo busca en cuatro zonas, por orden, de dentro hacia fuera. Se detiene en la primera donde lo encuentra.

OrdenZonaQué esEn el aeropuerto sería
LLocalDentro de la función que se está ejecutandoLa puerta de embarque
EEnvolvente (Enclosing)Las funciones exteriores que envuelven a esa funciónLa terminal
GGlobalEl módulo donde se definió la funciónEl aeropuerto
BIntegrado (Built-in)Nombres básicos como len, print o listEl país
zone = "G"

def terminal():
    gate = "E"
    def boarding():
        seat = "L"
        print(seat, gate, zone, len("abc"))
    boarding()

terminal()
# L E G 3
  • seat 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).
  • Si no está en ninguna de las cuatro zonas, es un NameError.
  • La zona no depende de desde dónde se llama a la función, sino de dónde está escrita. Aunque llames a 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

Los cuatro nombres de dentro de boarding se encuentran en L, E, G y B, respectivamente. El zone de dentro de broken se detiene en L aunque exista fuera.
  • Elige 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.

5-2. Donde se asigna el valor se decide el ámbito (10 min)

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 value

El 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))
# True
  • co_varnames es la lista de nombres locales que fijó el compilador. scanned ya está en ella.
  • Por eso la línea 4 busca «la etiqueta local 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)

En la primera versión, la casilla scan solo tiene la etiqueta bag; la etiqueta scanned está vacía. global usa la etiqueta de fuera, y la tercera recibe el valor como argumento y lo devuelve.

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.

5-3. Mover directamente la etiqueta de fuera: 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)
# 2
def terminal():
    boarded = 0
    def board():
        nonlocal boarded
        boarded += 1
    board()
    board()
    return boarded

print(terminal())
# 2
  • global 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.
  • Si el nombre declarado con nonlocal no existe en ninguna función envolvente, antes de ejecutar salta SyntaxError: no binding for nonlocal 'x' found.
  • La declaración tiene que ir antes de usar el nombre. Si primero asignas un valor y después escribes 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.

5-4. Lo que crea una zona y lo que no (5 min)

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 zonaNo crean zona
def, lambda, comprensiones de listas, conjuntos y diccionarios, expresiones generadoras, cuerpo de classif, for, while, with, try
  • Aunque el for termine, la variable del bucle i se queda con el último valor (2). Lo mismo pasa con los nombres creados dentro del bucle.
  • La 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.

5-5. Tapar nombres integrados (5 min)

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 callable

La 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 3
  • Si quitas la etiqueta global list, vuelve a verse el list integrado.
  • Los nombres integrados viven en el módulo builtins. Aunque estén tapados, siempre puedes sacarlos con builtins.list.
  • Esta es la respuesta al ejercicio 3 de la lección 1. Si después de 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.

5-6. Mirar dentro de las zonas: 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.
  • Si quieres crear nombres de forma dinámica, usa un diccionario, no locals().

Caso cerrado (10 min)

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
# 2

Han 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.

Ejercicios

  1. ¿Qué pasa si dentro del boarding de 5-1 añades una línea zone = "L" debajo del print? Compáralo con zone @ broken en el mapa de ámbitos.
  2. Sin usar 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.)
  3. Si pones 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.

Datos: tu correo electrónico · Finalidad: avisos de nuevas lecciones o de la publicación de este curso · Conservación: hasta que pidas que lo borremos. No lo usamos para nada más ni lo compartimos. Responde a cualquier aviso y lo borraremos enseguida.

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.

Se muestra como texto plano, sin enlaces ni formato.

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
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex