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 2

Tiempo 0:50–1:40

Maletas selladas: mutable e inmutable

Parte de equipaje perdido

LOST-002

La maleta sellada engordó

Lo reporta

Oh, responsable de inventario de la tienda duty free

Qué se perdió

Una maleta (tupla) con la pegatina de precinto. La báscula dio el error «una maleta sellada no se puede abrir», pero al volver a abrirla había un artículo más dentro.

Para cerrar el caso

Explicar por qué el artículo entró a pesar del error, y crear una maleta que de verdad no se pueda abrir.

Aeropuerto Internacional Haneulseom · Centro de equipajes

En la reunión de la mañana, la supervisora Do Miran levanta una pegatina de precinto. «Nadie puede abrir una maleta que lleve esto. Pero el parte de hoy dice que una maleta sellada ha engordado.»

El código que envía la tienda duty free es corto. Al intentar meter un artículo en una tupla, saltó un TypeError. Sin embargo, al imprimir la tupla otra vez, el artículo estaba dentro. La supervisora lo resume: «El precinto está en la maleta de fuera. Eso no precinta la maleta que va dentro.»

2-1. Maletas selladas y maletas abiertas (9 min)

Los objetos se dividen en dos grupos. Si su contenido puede cambiar después de crearlos, son mutables; si no puede cambiar, son inmutables.

GrupoTipos típicosEn el aeropuerto sería
Mutablelist, dict, setUna maleta con cremallera. Aunque metas o saques cosas, sigue siendo la misma maleta.
Inmutableint, float, str, tuple, frozenset, bool, NoneUna maleta sellada. Para cambiar el contenido hay que hacer una maleta nueva.

Los objetos inmutables simplemente no tienen ninguna forma de cambiar su contenido.

word = "gate"
print(word.upper(), word)
# GATE gate
word[0] = "G"
# TypeError: 'str' object does not support item assignment
fs = frozenset({1, 2})
fs.add(3)
# AttributeError: 'frozenset' object has no attribute 'add'

word.upper() no modifica word. Crea y devuelve una cadena nueva en mayúsculas. Todos los métodos de las cadenas funcionan así.

Entonces, ¿qué es n = n + 1? No se ha modificado la maleta del entero 10. Se obtiene un objeto entero nuevo, 11, y la etiqueta n se mueve hacia él. «Cambiar» un objeto inmutable siempre significa objeto nuevo + reasignación.

El tablero de abajo dibuja también los enteros como maletas propias. Fíjate adónde va la flecha de n después de la línea 3.

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, el entero 10 sigue donde estaba: aparece una maleta nueva, 11, y solo la etiqueta n se muda a ella. m sigue apuntando a 10.

2-2. Las dos caras de += (10 min)

a += b parece una sola operación. Pero Python la resuelve así:

  1. Si el tipo de a tiene el método __iadd__ (suma en el sitio), lo llama. Modifica la maleta en el sitio y devuelve esa misma maleta.
  2. Si no lo tiene, hace como a = a + b: crea un objeto nuevo con __add__ y mueve la etiqueta.
print(hasattr(list, "__iadd__"), hasattr(int, "__iadd__"))
# True False
print(hasattr(str, "__iadd__"), hasattr(tuple, "__iadd__"))
# False False

Las listas son mutables y tienen __iadd__. Los tipos inmutables no lo tienen: una maleta sellada no se puede modificar en el sitio. Por eso el mismo += se comporta de dos maneras.

a = [1, 2]
b = a
a += [3]
print(a, b, a is b)
# [1, 2, 3] [1, 2, 3] True

a = (1, 2)
b = a
a += (3,)
print(a, b, a is b)
# (1, 2, 3) (1, 2) False

a = "T1"
b = a
a += "-A"
print(a, b, a is b)
# T1-A T1 False
TipoQué hace a += x¿Se ve desde otra etiqueta b?
list__iadd__: extend sobre la misma maletaSí
tuple, str, int__add__: crea un objeto nuevo y solo mueve aNo

Por eso a = a + [3] de la lección 1 y a += [3] con una lista dan resultados distintos: el primero crea una lista nueva; el segundo usa la misma. |= en set y en dict también modifica en el sitio, igual que la lista.

Prueba los tres tipos uno tras otro.

Tablero de etiquetas

Paso 1 / 0

Lo siguiente que se ejecuta: 0

Preparando el laboratorio…

Acaba de cambiar

Maleta sellada (inmutable)

Con list, después de la línea 3 las dos flechas siguen apuntando a la misma maleta. Con tuple y str aparece una maleta nueva y solo a se muda. El 1 y el 2 de la tupla nueva son los mismos objetos entero que en la tupla original.

Pin dice

→ Si una función recibe una lista y hace x += [...], también cambia la lista de quien la llamó. Con un entero o una cadena, lo de fuera no cambiaría. Recuerda que la misma línea se comporta distinto según el tipo (lección 4).

2-3. El precinto solo cubre la maleta de fuera: listas dentro de tuplas (8 min)

Que una tupla sea inmutable significa, exactamente, que «no se pueden cambiar las referencias que guarda». Cada casilla apunta siempre al mismo objeto. Pero si ese objeto es una lista, la lista en sí puede cambiar todo lo que quiera.

t = (1, [2])
t[1].append(3)
print(t)
# (1, [2, 3])
t[1] = [9]
# TypeError: 'tuple' object does not support item assignment

El código del parte usaba += en lugar de append. Y aquí pasa algo curioso:

t = (1, [2])
try:
    t[1] += [3]
except TypeError as e:
    print("TypeError:", e)
print(t)
# TypeError: 'tuple' object does not support item assignment
# (1, [2, 3])

Hubo un error y, aun así, la lista cambió. Es porque t[1] += [3], desglosado, son dos pasos:

t = (1, [2])
tmp = t[1]
tmp = tmp.__iadd__([3])   # paso 1: modifica la lista en el sitio (funciona)
print(t)
# (1, [2, 3])
t[1] = tmp                # paso 2: la vuelve a poner en la casilla de la tupla (falla)
# TypeError: 'tuple' object does not support item assignment

En el paso 1 la lista ya ha cambiado. La asignación a la casilla del paso 2 falla y lanza el error, pero no hay forma de deshacer lo anterior. Por eso la báscula gritaba «una maleta sellada no se puede abrir» mientras el artículo entraba.

Tablero de etiquetas

Paso 1 / 0

Lo siguiente que se ejecuta: 0

Preparando el laboratorio…

Acaba de cambiar

Maleta sellada (inmutable)

En el último paso aparece el error, pero la lista del dibujo ya contiene el 3. La casilla de la tupla sigue apuntando a la misma lista.

Hay una forma sencilla de saber si algo está sellado hasta dentro: comprobar si hash() funciona. Una tupla con hash solo contiene objetos que también tienen hash (normalmente, inmutables) (lección 12).

print(hash(("passport", ("snack",))) == hash(("passport", ("snack",))))
# True
hash(("passport", ["snack"]))
# TypeError: unhashable type: 'list'

2-4. El coste de unir cadenas con += (6 min)

Como las cadenas son inmutables, s += "x" crea en principio una cadena nueva cada vez. Si construyes una cadena de longitud n letra a letra, el número de caracteres copiados crece como 1 + 2 + … + n.

def plus(n):
    s = ""
    for i in range(n):
        s += "x"
    return s

def plus_shared(n):
    s = ""
    for i in range(n):
        last = s          # la misma cadena lleva una etiqueta más
        s += "x"
    return s

def join(n):
    return "".join(["x" for i in range(n)])

print(plus(5) == plus_shared(5) == join(5))
# True

En CPython, si una cadena tiene una sola etiqueta, += aplica a escondidas una optimización que la alarga en el sitio. Por eso plus es más rápido de lo que cabría esperar. Pero basta con una etiqueta más para que la optimización desaparezca. Estas son medidas tomadas en este PC (CPython 3.14.2):

Iteracionesplusplus_sharedjoin
100 000unos 6 msunos 120 msunos 3 ms
200 000unos 12 msunos 460 msunos 5 ms

Al duplicar las iteraciones, plus_shared tardó casi cuatro veces más: la cantidad copiada crece con el cuadrado. Esa optimización no es una promesa del lenguaje, así que, cuando acumules cadenas en un bucle, guárdalas en una lista y únelas de una vez con "".join().

2-5. Enteros pequeños e interning: por qué no se usa is para comparar valores (7 min)

Un objeto inmutable nunca va a cambiar, así que es seguro que varios compartan un único objeto con el mismo valor. CPython lo aprovecha para crear de antemano los objetos que se usan a menudo.

Caché de enteros pequeños. CPython crea al arrancar los enteros del -5 al 256 y siempre devuelve esos mismos objetos.

print(int("256") is int("256"), int("257") is int("257"))
# True False
print(int("-5") is int("-5"), int("-6") is int("-6"))
# True False

Creamos los enteros en tiempo de ejecución con int("…") a propósito. Si escribes literales directamente en el código, como 257 is 257, el compilador puede fusionar las constantes iguales en una y dar True. Python incluso avisa de este tipo de comparaciones:

x = 256
print(x is 256)
# SyntaxWarning: "is" with 'int' literal. Did you mean "=="?
# True

Interning de cadenas. CPython reúne en un solo objeto las constantes de cadena que parecen nombres (interning). Las cadenas creadas durante la ejecución pueden ser objetos distintos aunque tengan el mismo valor. Con sys.intern() puedes hacer que las cadenas con el mismo valor sean un único objeto.

import sys
a = "gate"
b = "".join(["ga", "te"])
print(a == b, a is b)
# True False
print(sys.intern(b) is a)
# True

Como el resultado depende de la caché y del interning, comparar valores con is acierta con unos valores y falla con otros. Los valores se comparan siempre con ==; is se reserva para objetos únicos como None.

Pin dice

→ Si en las pruebas solo usas números pequeños, x is 100 siempre da True y el error se esconde. En cuanto en producción llega un 1000, da False. Compara números y cadenas con ==.

Caso cerrado (10 min)

Este es el código del parte y la forma de crear una maleta que de verdad no se pueda abrir.

sealed = ("passport", ["snack"])
try:
    sealed[1] += ["perfume"]
except TypeError as e:
    print("scale:", e)
print(sealed)
# scale: 'tuple' object does not support item assignment
# ('passport', ['snack', 'perfume'])

sealed = ("passport", ("snack",))   # también una tupla por dentro
try:
    sealed[1] += ("perfume",)
except TypeError as e:
    print("scale:", e)
print(sealed)
# scale: 'tuple' object does not support item assignment
# ('passport', ('snack',))
  • La primera maleta solo estaba sellada por fuera. El __iadd__ de la lista interior funcionó primero, y lo único que falló fue la asignación a la casilla.
  • La segunda maleta también es una tupla por dentro. Las tuplas no tienen __iadd__, así que solo se crea una tupla nueva y la asignación a la casilla falla. La maleta original queda intacta.

La supervisora pega un precinto nuevo también en la maleta interior. «El precinto no termina por fuera. Hay que mirar también las maletas que van dentro.»

Informe de cierre — LOST-002. La tupla solo fija las referencias que guarda; la lista de dentro era mutable. t[1] += [...] amplía la lista en el sitio y luego falla al asignar la casilla. Resuelto haciendo inmutable (tupla) también el interior.

Ejercicios

  1. Después de ejecutar x = 1000, y = x y x += 1, ¿qué vale y? Dibuja las etiquetas y luego compruébalo.
  2. Si ejecutas s = {1}, t = s y s |= {2}, ¿qué le pasa a t? Prueba también cambiándolo por frozenset({1}).
  3. Con t = ([],), ¿cómo termina cada una de estas, t[0] += [1] y t[0].extend([1])? Después de ejecutarlas, imprime también t.

La libreta de la supervisora

  • Mutables: list, dict, set. Inmutables: int, float, str, tuple, frozenset.

  • «Cambiar» un objeto inmutable siempre es crear un objeto nuevo y mover la etiqueta.

  • += modifica en el sitio si hay __iadd__ (list); si no, crea un objeto nuevo (int, str, tuple).

  • Una tupla solo fija las referencias que guarda. La lista de dentro cambia. t[1] += [...] lanza un error y aun así la cambia.

  • Para acumular cadenas en un bucle, guárdalas en una lista y usa "".join().

  • La caché de enteros pequeños y el interning son cosas de CPython. Los valores se comparan siempre con ==.

← Anterior

Lección 1 · La etiqueta no es la maleta: nombres y objetos

Siguiente →

Lección 3 · Maletas dentro de maletas: contenedores y copias

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