Python tiene una variedad de tipos de datos, algunos de los cuales pueden modificarse después de su creación y otros no. Cuando hablamos de tipos que pueden modificarse, nos referimos a ellos como tipos "mutables". En contraste, aquellos que no pueden cambiarse se denominan tipos "inmutables".
Tipos Mutables
Los tipos mutables más comunes son las list, dict y set. Aquí hay un ejemplo de una lista:
Con una list, puedes cambiar sus elementos, agregar nuevos o eliminar los existentes:
fruits[1] = 'blueberry' # Cambia 'banana' a 'blueberry'
fruits.append('dragonfruit') # Agrega 'dragonfruit' al final de la lista
print(fruits) # ['apple', 'blueberry', 'cherry', 'dragonfruit']
Tipos Inmutables
Por otro lado, ejemplos de tipos inmutables son int, float, string y tuple. Una vez creados, no puedes cambiar su contenido. Aquí hay un ejemplo de un string:
Si intentas cambiar parte del string, Python no lo permitirá:
message[0] = 'H' # Intentar cambiar 'h' por 'H' te dará un TypeError
¿Cómo es que int es inmutable? ¿No podemos cambiar el valor con += o -=?
Cuando hablamos de mutabilidad en Python, nos referimos a que el valor real almacenado en memoria puede ser modificado. Por ejemplo, cuando tenemos una lista, podemos cambiar un elemento de la lista y sigue siendo la misma lista en memoria.
Los enteros en Python son inmutables. Esto significa que el valor en memoria no puede ser cambiado. Si tenemos un entero, a = 5, y realizamos una operación como a += 2, podría parecer que cambiamos a. Sin embargo, lo que Python realmente hizo fue crear un nuevo objeto en memoria con un valor de 7 y actualizó a para que apunte a este nuevo objeto. El 5 original no fue cambiado en memoria, por eso los int se consideran inmutables.
a = 5
print(id(a)) # Esto imprime la ubicación en memoria de a. Digamos que es 1001
a += 2
print(a) # Esto imprime 7
print(id(a)) # Esto imprimirá una ubicación de memoria diferente, digamos que es 1002
La función id en Python devuelve la identidad de un objeto, que es única y constante para el objeto durante su vida útil. Entonces, cuando vemos que la identidad de a cambió después de la operación a += 2, es una señal clara de que a ahora apunta a un nuevo objeto en memoria. El entero original 5 no fue cambiado, lo que confirma que los enteros son de hecho inmutables en Python.
Argumentos Mutables en Funciones
Cuando pasamos un objeto mutable (como una lista) a una función, Python le da a la función una referencia a ese objeto. Esto significa que si la función modifica el objeto mutable, esa modificación se verá también fuera de la función.
Usemos una función que recibe una lista y le agrega un elemento:
def add_item(my_list):
my_list.append('added item') # Agrega un nuevo elemento a la lista
shopping_list = ['apples', 'bananas', 'cherries']
print(shopping_list) # Prints: ['apples', 'bananas', 'cherries']
# Ahora usamos la función para agregar un elemento a nuestra lista de compras
add_item(shopping_list)
print(shopping_list) # Prints: ['apples', 'bananas', 'cherries', 'added item']
Como puedes ver, cuando imprimimos shopping_list después de llamar a la función add_item, la lista tenía un nuevo elemento. Esto sucedió aunque no modificamos directamente shopping_list fuera de la función.
Sin embargo, es importante entender que si asignas un nuevo valor a todo el objeto mutable dentro de la función, eso no afectará al objeto original. Esto se debe a que le estás dando a la variable local de la función una nueva referencia, no modificando el objeto original. Veamos un ejemplo de esto:
def change_list(my_list):
my_list = ['entirely', 'new', 'list'] # Esto no afectará a la lista original
shopping_list = ['apples', 'bananas', 'cherries']
change_list(shopping_list)
print(shopping_list) # Still prints: ['apples', 'bananas', 'cherries']
Aunque llamamos a la función change_list con shopping_list como argumento, el contenido de shopping_list no cambió. Eso es porque dentro de la función, my_list recibió una nueva referencia (a una lista completamente nueva), mientras que la referencia de shopping_list permaneció intacta.
La clave aquí es: cuando pasas un objeto mutable a una función en Python, recuerda que si la función cambia el objeto (como agregar un elemento a una lista), el cambio es permanente y se verá fuera de la función. Pero si le das un nuevo valor a todo el objeto dentro de la función, eso no afectará al objeto original.