Aprende leyendo en orden

Tipos mutables e inmutables

Comprende los tipos mutables e inmutables de Python y por qué y = x puede provocar bugs por referencias compartidas.

Por qué necesitas aprender esto

Este artículo no es para principiantes absolutos, pero el tema es imposible de evitar si quieres entender la programación a fondo. Es una historia habitual: un principiante se topa con un bug de mutabilidad y pasa horas depurándolo.

En Python, puedes pensar que y = x solo conecta dos variables con un signo igual, y descubrir que al editar una se reescribe también la otra. Eso es la mutabilidad.

Este capítulo aclara la diferencia entre «mutable» (modificable) e «inmutable» (no modificable), y te enseña cómo copiar de forma segura.

Clasificación de tipos — a qué lado pertenece cada uno
Inmutableint, floatstr, bool, tupleUno cambia,otro se quedaMutablelist, dictsetUno cambia,ambos cambianincluyerasgoincluyerasgo

Un tipo que permite reescribir la propia variable (con append, asignación de elementos, update, etc.) es mutable; el que no lo permite es inmutable.

list / dict / set son mutables, y el resto (int / float / str / bool / tuple) es inmutable.

Tipos inmutables — cambiar uno no afecta al otro

Con un tipo inmutable (un tipo no modificable), una vez que has pasado el valor con y = x, cambiar x después no tiene efecto sobre y.

Por ejemplo, después de x += 1, x vale 11, pero y se queda en el valor original 10.

Cómo fluye la asignación para tipos inmutables
x = 10y = xx += 1x = 11y = 10ejecutaresultado
# int es inmutable
x = 10
y = x
x += 1
print(x)   # 11
print(y)   # 10  <- sin cambios

# str también es inmutable
x = "hello"
y = x
x = x + " world"
print(x)   # hello world
print(y)   # hello

# tuple también es inmutable
x = ("a", "b")
y = x
x = ("c", "d")
print(x)   # ('c', 'd')
print(y)   # ('a', 'b')

Con los tipos inmutables, una vez que guardas un valor en otra variable, actualizar la variable original no afecta a la copia.

① Copia un precio de producto price = 1000 en otra variable old_price, y luego sube el precio con price = price + 500. Imprime price y old_price.

② Copia una tupla de tags tags = ("sale", "new") en old_tags, y luego reasigna tags a otra tupla con tags = ("limited", "gift"). Imprime tags y old_tags.

(Si aciertas ambas partes, aparecerá la explicación.)

Editor Python

Ejecutar el código para ver el resultado

Las variables de Python son «etiquetas pegadas en cajas»

Una variable en Python no contiene el valor en sí — es más bien una etiqueta que apunta a unos datos (una caja) en la memoria. Cuando escribes y = x, Python no crea una caja nueva; solo pega otra etiqueta (y) sobre la misma caja a la que apunta x.

- Tipos inmutables: una reasignación como x = 11 simplemente mueve la etiqueta de x a otra caja. y sigue apuntando a la caja original, así que su valor se queda independiente.

- Tipos mutables: una operación in-place como x.append(...) modifica la caja compartida en sí, así que el cambio es visible también a través de y.

Esa es la línea que separa lo inmutable de lo mutable.

Tipos mutables — con y = x, cambiar uno cambia el otro

En cambio, con un tipo mutable (list / dict / set), escribir y = x deja a y y x apuntando a la misma caja.

Si luego haces algo que reescribe el contenido, como x.append(...), el cambio aparece también a través de y.

Asignación con tipos mutables — compartiendo la misma caja
x=[a,b]y=xx.append(c)x=[a,b,c]y=[a,b,c]ejecutaambos

y = x no crea una copia separada: ambos nombres apuntan al mismo lugar.

Las operaciones que reescriben el contenido, como append, modifican la caja compartida a la que ambas etiquetas hacen referencia, por lo que el cambio también es visible desde y.

# list es mutable
x = ["a", "b"]
y = x          # solo añade otra etiqueta y a la misma lista

x.append("c")  # reescribe el contenido directamente
print(x)       # ['a', 'b', 'c']
print(y)       # ['a', 'b', 'c']  <- ¡y también creció!

# remove se comporta igual
x.remove("b")
print(y)       # ['a', 'c']  <- desaparece también de y

# dict y set muestran el mismo comportamiento
d = {"k": 1}
e = d
e["new"] = 99
print(d)       # {'k': 1, 'new': 99}  <- d también creció

¿Por qué compartir es el comportamiento por defecto?

Si y = x copiara todo el contenido cada vez, entonces cuando, por ejemplo, x contenga millones de registros traídos de una base de datos, la memoria y el tiempo de ejecución se dispararían rápido.

Por eso en Python el nombre de una variable no es el valor en sí, sino una etiqueta que apunta a una ubicación en la memoria.

No puedes cambiar este mecanismo, así que depende de ti (el que escribe) usarlo con cuidado.

Veamos la compartición que provocan los tipos mutables.

① Crea un carrito de compra cart = ["leche", "pan"] y luego escribe old_cart = cart (con la intención de copiarlo).

② Creyendo que solo añades "huevo" a cart con cart.append("huevo"), imprime después cart y old_cart.

Ambos deberían contener "huevo". Esa es la trampa de hoy.

Editor Python

Ejecutar el código para ver el resultado

Usa copy() para obtener una versión independiente

Cuando quieras mantener los datos originales y crear una versión separada, usa el método copy().

Escribir y = x.copy() copia el contenido a una caja nueva y se la entrega a y, así que los cambios posteriores en x ya no afectan a y.

copy() crea una «caja separada»
x=[a,b]y=x.copy()x.append(c)x=[a,b,c]y=[a,b]ejecutaindependiente

En el momento de y = x.copy() se reserva una nueva región de memoria.

Después de eso, reescribir el contenido de x con x.append(...) o similar no tiene ningún efecto sobre y.

# list / dict / set tienen .copy()
x = ["apple", "lemon"]
y = x.copy()

x.append("grape")
print(x)   # ['apple', 'lemon', 'grape']
print(y)   # ['apple', 'lemon']  <- sin efecto

# dict.copy() funciona igual
d = {"a": 1, "b": 2}
e = d.copy()
d["c"] = 3
print(d)   # {'a': 1, 'b': 2, 'c': 3}
print(e)   # {'a': 1, 'b': 2}  <- sin efecto

# set.copy() funciona igual
s = {1, 2}
t = s.copy()
s.add(3)
print(s)   # {1, 2, 3}
print(t)   # {1, 2}            <- sin efecto

# list tiene otras formas de copiar
x = ["apple", "lemon"]
y1 = list(x)   # pasar por el constructor da una lista nueva
y2 = x[:]      # copia completa con slice (del inicio al final)

Cuidado cuando tu lista contiene otras listas

copy() solo construye una nueva caja exterior. Los valores mutables que están dentro (como listas dentro de una lista) siguen compartidos. Esto se llama copia superficial (shallow copy).

Ten cuidado con los datos anidados.

Corrijamos con copy() el bug de la sección anterior.

Crea cart = ["leche", "pan"], pero esta vez escribe old_cart = cart.copy(). Ejecuta cart.append("huevo") y luego imprime cart y old_cart. Esta vez, «huevo» no debería acabar en old_cart.

Editor Python

Ejecutar el código para ver el resultado

En este artículo has aprendido la diferencia entre tipos mutables e inmutables y cómo copy() te da una versión independiente.

La idea de que un nombre de variable no es el valor en sí, sino una etiqueta que apunta a una ubicación en la memoria también se aplica a otros lenguajes, no solo a Python. Cuando trabajes con tipos mutables, intercala un copy().

QUIZ

Verificación de conocimientos

Responde cada pregunta una a una.

Pregunta 1¿Cuál de estos grupos contiene solo tipos mutables?

Pregunta 2¿Cuál es el valor de b después de ejecutar el siguiente código?

``
a = [1, 2, 3]
b = a
a.append(4)

Pregunta 3Quieres conservar el contenido de la lista original a y obtener una versión independiente b. ¿Cuál es la forma más adecuada de escribirlo?