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 1:40–2:30
Parte de equipaje perdido
LOST-003
Lo reporta
El profesor Yoon, que acompaña el viaje escolar de un instituto
Qué se perdió
Los kits de material de cada clase. «Clonó» un kit básico y lo repartió entre la clase 1 y la clase 2, pero al derramar agua en la maleta de la clase 1 también se mojaron las toallas de la clase 2.
Para cerrar el caso
Averiguar hasta dónde los kits de las dos clases eran la misma maleta, y crear kits separados hasta el fondo.
Aeropuerto Internacional Haneulseom · Centro de equipajes
A las diez de la mañana, el profesor llega al centro con una foto de las toallas mojadas. Asegura que clonó el kit con copy(), y aun así las toallas de las dos clases se mojaron a la vez.
La supervisora Do Miran le da la vuelta al kit para enseñárselo. Hay dos maletas exteriores. Pero dentro de cada maleta exterior no hay objetos, sino etiquetas. «Solo has comprado maletas exteriores nuevas. Las etiquetas que van dentro apuntan a la misma bolsa de toallas.»
Los contenedores como las listas, las tuplas, los dict o los set no guardan los objetos en sí. Guardan referencias a los objetos, es decir, etiquetas. Igual que en la lección 1 un nombre era una etiqueta, cada casilla de una lista también es una etiqueta.
Por eso el mismo objeto puede estar en varias casillas a la vez.
row = [0, 0]
pair = [row, row]
row.append(7)
print(pair)
# [[0, 0, 7], [0, 0, 7]]
print(pair[0] is pair[1])
# TrueDonde más accidentes provoca esta propiedad es en las listas bidimensionales.
grid = [[0] * 3] * 3
grid[0][0] = 9
print(grid)
# [[9, 0, 0], [9, 0, 0], [9, 0, 0]]
print(grid[0] is grid[1] is grid[2])
# True[0] * 3 crea una lista. El * 3 de fuera no copia esa lista tres veces: mete tres veces una etiqueta que apunta a la misma lista. Una maleta, tres etiquetas.
En cambio, el [0] * 3 de dentro no da problemas. Todas las casillas apuntan al mismo entero 0, pero los enteros son inmutables, así que row[0] = 9 solo mueve la etiqueta de esa casilla al 9. Compartir solo es un problema cuando se comparte un objeto mutable.
La solución es una comprensión que crea una lista nueva en cada fila.
grid = [[0] * 3 for _ in range(3)]
grid[0][0] = 9
print(grid)
# [[9, 0, 0], [0, 0, 0], [0, 0, 0]]Tablero de etiquetas
Paso 1 / 0
Lo siguiente que se ejecuta: 0
Preparando el laboratorio…
Acaba de cambiar
Maleta sellada (inmutable)
Hay cinco formas habituales de copiar una lista. Todas dan el mismo resultado.
import copy
kit = [["water", "map"], ["snack"]]
copies = [kit[:], list(kit), kit.copy(), copy.copy(kit)]
print([c is kit for c in copies])
# [False, False, False, False]
print([c[0] is kit[0] for c in copies])
# [True, True, True, True]Esto es una copia superficial (shallow copy). Se crea una maleta exterior nueva, pero las etiquetas de dentro se trasladan tal cual. Por eso no da lo mismo modificar la maleta exterior que modificar una maleta interior.
a = kit[:]
a.append(["hat"]) # añade una casilla a la maleta exterior: el original no cambia
print(len(kit), len(a))
# 2 3
a[0].append("towel") # modifica una maleta interior: se ve también en el original
print(kit[0])
# ['water', 'map', 'towel']Con dict pasa lo mismo. d.copy(), dict(d) y {**d} son copias superficiales.
rooms = {"class1": ["water"], "class2": ["map"]}
dup = rooms.copy()
dup["class3"] = [] # clave nueva: el original no la tiene
dup["class1"].append("tea") # lista compartida: se ve también en el original
print(rooms)
# {'class1': ['water', 'tea'], 'class2': ['map']}Abajo puedes aplicar =, una copia superficial y una copia profunda a la misma lista anidada. La línea 4 modifica una maleta interior y la línea 5 añade una casilla a la maleta exterior.
Tablero de etiquetas
Paso 1 / 0
Lo siguiente que se ejecuta: 0
Preparando el laboratorio…
Acaba de cambiar
Maleta sellada (inmutable)
Pin dice
→ La copia superficial crea de nuevo «un solo nivel». Si dentro solo hay números o cadenas, basta con ella; si hay listas o dicts, piensa si necesitas una copia profunda.
copy.deepcopy baja por las maletas dentro de la maleta, y por las maletas dentro de esas, y lo crea todo de nuevo.
import copy
kit = [["water", "map"], ["snack"]]
deep = copy.deepcopy(kit)
deep[0].append("rain")
print(kit[0], deep[0], deep[0] is kit[0])
# ['water', 'map'] ['water', 'map', 'rain'] FalseUna copia que baja hasta el fondo tiene dos trampas: encontrarse dos veces con la misma maleta, y una maleta que se contiene a sí misma, de modo que nunca se llega al final. deepcopy resuelve las dos con un registro llamado memo. Apunta los objetos ya copiados como {id(original): copia} y, si vuelve a encontrarse uno, usa la copia del registro.
x = [1]
pair = [x, x]
p2 = copy.deepcopy(pair)
print(p2[0] is p2[1], p2[0] is x)
# True False
memo = {}
copy.deepcopy(pair, memo)
print(id(x) in memo)
# TrueDos casillas que en el original eran la misma maleta también lo son en la copia. Se copian incluso las relaciones de compartición.
Una lista que se contiene a sí misma tampoco provoca un bucle infinito. Al imprimir una lista así, Python la abrevia como [...].
a = []
a.append(a)
print(a)
# [[...]]
b = copy.deepcopy(a)
print(b[0] is b, b is a)
# True FalseTablero de etiquetas
Paso 1 / 0
Lo siguiente que se ejecuta: 0
Preparando el laboratorio…
Acaba de cambiar
Maleta sellada (inmutable)
__copy__ y __deepcopy__. Basta con saber que existen.Un objeto inmutable nunca va a cambiar, así que no hay motivo para copiarlo. copy devuelve el propio original.
import copy
t = (1, 2)
print(copy.copy(t) is t, copy.deepcopy(t) is t)
# True True
s = "gate"
print(copy.deepcopy(s) is s)
# TrueAhora bien, si la tupla contiene un objeto mutable, la cosa cambia. La copia superficial sigue devolviendo el original, pero la copia profunda tiene que crear de nuevo la lista de dentro, así que crea una tupla nueva.
t2 = (1, [2])
print(copy.copy(t2) is t2)
# True
dt = copy.deepcopy(t2)
print(dt is t2, dt == t2, dt[1] is t2[1])
# False True FalseLo mismo ocurre con las cadenas dentro de una lista copiada en profundidad. La lista es nueva, pero el "map" de dentro es el mismo objeto cadena que en el original.
kit = [["map"], ["snack"]]
deep = copy.deepcopy(kit)
print(deep[0] is kit[0], deep[0][0] is kit[0][0])
# False TrueNi siquiera la copia profunda vuelve a empaquetar las maletas selladas: lo que no puede cambiar se puede compartir sin peligro.
copy.replace(): un objeto nuevo con solo una parte cambiada (5 min)Cuando quieres «modificar» un objeto inmutable, creas uno nuevo con solo una parte cambiada. Desde 3.13, copy.replace() lo hace de una única forma para todos. Lo admiten objetos como las dataclasses con frozen=True, namedtuple o datetime.date.
import copy
from dataclasses import dataclass
from collections import namedtuple
@dataclass(frozen=True)
class Ticket:
gate: str
seat: str
tk = Ticket("A1", "12C")
tk2 = copy.replace(tk, seat="14F")
print(tk, tk2)
# Ticket(gate='A1', seat='12C') Ticket(gate='A1', seat='14F')
Pass = namedtuple("Pass", "gate seat")
print(copy.replace(Pass("A1", "12C"), gate="B7"))
# Pass(gate='B7', seat='12C')
tk.seat = "1A"
# dataclasses.FrozenInstanceError: cannot assign to field 'seat'El original tk no cambia: aparece un objeto nuevo que solo se diferencia en el campo modificado. Si lo usas con un objeto que no lo admite (una lista, por ejemplo), obtienes TypeError: replace() does not support list objects.
Volvamos al código del profesor.
import copy
kit = [["towel", "notebook"], ["raincoat"]]
class1 = kit.copy()
class2 = kit.copy()
print(class1 is class2, class1[0] is class2[0])
# False True
class1[0][0] = "wet towel"
print(class2[0])
# ['wet towel', 'notebook']Las maletas exteriores eran distintas, pero la etiqueta de la primera casilla apuntaba a la misma bolsa de toallas. El agua cayó en esa única bolsa. Hay dos formas de arreglarlo.
kit = [["towel", "notebook"], ["raincoat"]]
class1 = copy.deepcopy(kit)
class2 = copy.deepcopy(kit)
class1[0][0] = "wet towel"
print(class2[0], class1[0] is class2[0])
# ['towel', 'notebook'] False
def new_kit():
return [["towel", "notebook"], ["raincoat"]] # una maleta nueva en cada llamada
print(new_kit()[0] is new_kit()[0])
# FalseTener una función que crea el kit elimina el original que habría que copiar. Cada llamada lo empaqueta todo desde cero, así que no hay nada que compartir.
Cuando el profesor se marcha, Pin muestra en pantalla una radiografía de la cinta 13. Las dos maletas fantasma aparecen como si cada una estuviera dentro de la otra. La supervisora escribe una línea en la pantalla.
a = []
b = [a]
a.append(b)
print(a)
# [[[...]]]«Maletas que se llevan la una a la otra dentro. Quizá por eso no las retiran aunque no tengan etiquetas.» La supervisora guarda la foto en una carpeta.
Informe de cierre — LOST-003. copy() hace una copia superficial, así que las dos clases compartían la bolsa de material de dentro. Resuelto con copy.deepcopy o con una función que crea un kit nuevo.
board = [[]] * 2 le haces board[0].append("x"), ¿en qué se convierte board? Arréglalo con una comprensión.d = {"a": [1]}, haz e = dict(d) y predice si e["a"].append(2) y e["b"] = [3] se ven en d.x = [1], y = [x, x] y z = copy.deepcopy(y), ¿qué da z[0] is z[1]? Piensa también qué pasaría si no existiera memo.La libreta de la supervisora
Los contenedores no guardan objetos, sino referencias (etiquetas). El mismo objeto puede estar en varias casillas.
[[0] * 3] * 3 es la misma lista tres veces. Las listas bidimensionales, con una comprensión.
Copia superficial (lst[:], list(), .copy(), copy.copy): solo lo de fuera es nuevo; lo de dentro se comparte.
Copia profunda (copy.deepcopy): todo nuevo hasta el fondo. Con memo, lo compartido y los ciclos se copian una sola vez.
Un objeto inmutable copiado sigue siendo el mismo objeto. Si dentro hay algo mutable, solo deepcopy crea uno nuevo.
copy.replace(): un objeto nuevo con solo una parte de un objeto inmutable cambiada (3.13).
← Anterior
Lección 2 · Maletas selladas: mutable e inmutable
Siguiente →
Lección 4 · Al entregar una maleta: funciones y argumentos
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.