Pregunta 1¿Cuál de estos grupos contiene solo tipos mutables?
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.
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.
# 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')
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.
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.
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.
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.
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().
Verificación de conocimientos
Responde cada pregunta una a una.
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?