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 3

Tiempo 1:40–2:30

Maletas dentro de maletas: contenedores y copias

Parte de equipaje perdido

LOST-003

El agua derramada en la maleta de la clase 1 empapó la de la clase 2

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

3-1. Dentro de la maleta hay etiquetas (10 min)

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])
# True

Donde 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)

Con [[0] * 3] * 3, las flechas de las tres casillas de la lista exterior convergen en una sola maleta. La comprensión crea una maleta nueva para cada fila.

3-2. Copia superficial: solo la maleta de fuera es nueva (10 min)

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)

Con =, incluso la maleta exterior es la misma, así que las líneas 4 y 5 se ven en kit. Con kit[:], solo la maleta exterior es nueva, así que solo se ve la línea 4. copy.deepcopy crea de nuevo también las listas interiores, y kit queda intacto.

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.

3-3. Copia profunda: todo nuevo hasta el fondo (9 min)

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'] False

Una 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)
# True

Dos 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 False

Tablero de etiquetas

Paso 1 / 0

Lo siguiente que se ejecuta: 0

Preparando el laboratorio…

Acaba de cambiar

Maleta sellada (inmutable)

Después de la línea 3, la casilla de la lista apunta a la propia lista. La copia de deepcopy también es una lista nueva que se apunta a sí misma, sin ninguna conexión con el original.
  • Las clases definidas por el usuario pueden decidir cómo se copian definiendo los métodos __copy__ y __deepcopy__. Basta con saber que existen.
  • La copia profunda recorre todo el grafo de objetos, así que es lenta y consume mucha memoria. A menudo es mejor crear de nuevo solo los niveles que hacen falta.

3-4. Las maletas selladas no se copian (6 min)

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)
# True

Ahora 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 False

Lo 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 True

Ni siquiera la copia profunda vuelve a empaquetar las maletas selladas: lo que no puede cambiar se puede compartir sin peligro.

3-5. 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.

Caso cerrado (10 min)

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])
# False

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

Ejercicios

  1. Si a board = [[]] * 2 le haces board[0].append("x"), ¿en qué se convierte board? Arréglalo con una comprensión.
  2. Con d = {"a": [1]}, haz e = dict(d) y predice si e["a"].append(2) y e["b"] = [3] se ven en d.
  3. Con 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.

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