En Python, a diferencia de otros lenguajes, no hay una separación entre tipos primitivos y tipos de referencia. Todos los tipos de datos en Python se pasan por referencias. ¿Por qué necesitas saber esto?
Desde el punto de vista del programador de aplicaciones, la diferencia se manifiesta al modificar datos, transmitirlos y devolverlos de funciones. Es importante tener en cuenta que las listas, como colecciones, no almacenan los valores en sí, sino referencias a ellos.
Al mismo tiempo, las listas en sí también se pasan por referencia. Para comprobar esto, crea varias variables que contengan una lista y observa cómo cambian:
items = [1, 2]
items2 = items
items2[0] = "python"
print(items2) #=> ["python", 2]
print(items) #=> ["python", 2]
En el ejemplo anterior, creamos una nueva variable items2 y guardamos en ella una referencia a la variable items. Ahora ambas variables apuntan a la misma lista. Por lo tanto, si cambiamos la lista en cualquiera de las variables, también cambiará para la otra.
Referencias
Una referencia 🔗 es un identificador único de un objeto, una dirección condicional en la memoria virtual del intérprete, donde se almacena el valor de la variable. Puedes obtener esta dirección con la función id()
a = 42
id(a) # 138620829632016
Un identificador 🆔 es simplemente un número que se asigna a cada objeto de forma única. Esto significa que, sin importar cómo accedamos al objeto en el código, su identificador siempre será el mismo. Por eso, los identificadores son útiles para seguir el rastro de las referencias a objetos y cómo se transfieren entre diferentes partes del código.
a = "some string"
b = a
id(a) # 139739990935280
id(b) # 139739990935280
print(a is b) # => True
Cuando creamos una variable y almacenamos un valor en ella, es como si le diéramos un nombre a la referencia. Luego, asignamos una variable a otra y le damos un nuevo nombre a esa misma referencia. Por eso id(a) y id(b) devuelven el mismo resultado.
El operador is verifica la igualdad de los identificadores de sus operandos. En este ejemplo, ambas variables apuntan al mismo objeto, por lo que la comprobación a is b da True.
En Python, el control is se utiliza cuando estamos lidiando con lo que se llama objetos singulares. Los signos únicos más conocidos en Python son True, False y None. Por lo tanto, la verificación de la igualdad a None generalmente se escribe así:
...
if foo is None:
...
Comparación de listas
El operador == compara listas, y cualquier otro objeto, por valor. Es decir, dos listas serán iguales si tienen los mismos valores:
[1, 2, 3] == [1, 2, 3] # True
Las listas también se pueden comparar por referencia.
items = [1, 2, 3]
items2 = [1, 2, 3]
print(items2 == items) # True
print(items2 is items) # False
En este ejemplo, aunque las listas contienen los mismos valores, cada lista apunta a su propia dirección en la memoria virtual.
Diseño de funciones
Si pasas una lista a alguna función que la modifica, la lista también cambiará. Después de todo, la función recibe exactamente una referencia a la lista. Mira el siguiente ejemplo:
def append_wow(some_list):
some_list.append('wow')
items = ['one']
append_wow(items)
print(items) # => ['one', 'wow']
append_wow(items)
print(items) # => ['one', 'wow', 'wow']
Al diseñar funciones que trabajan con listas, hay dos caminos a seguir: cambiar la lista original o formar una nueva dentro de la función y devolverla. ¿Cuál es mejor? En la mayoría de los casos, es mejor optar por el segundo. Es seguro. Las funciones que devuelven nuevos valores son más convenientes de usar y el comportamiento del programa se vuelve en general más predecible, ya que no hay cambios de datos incontrolables.
Cambiar la lista puede tener efectos inesperados. Imagina una función last(), que extrae el último elemento de la lista. Podría haber sido escrita así:
def last(items):
# El método .pop() extrae el último elemento de la lista
# Cambia la lista quitando este elemento
return items.pop()
items = [1, 2, 3]
last_item = last(items)
print(last_item) # 3
print(items) # [1, 2]
En algún lugar del código, solo querías ver el último elemento. Además de esto, la función para extraer este elemento, lo tomó y lo eliminó. Este comportamiento es muy inesperado para una función similar. Contradice muchos principios para construir buen código (por ejemplo, la "separación de comandos y consultas", este principio se discute en el curso de funciones). La implementación correcta de esta función se ve así:
# La lista no cambia
# El índice -1 significa el primer elemento desde el final
def last(items):
return items[-1]
items = [1, 2, 3]
print(last(items)) # => 3
print(items) # => [1, 2, 3]
¿En qué casos deberías cambiar la lista? Hay exactamente una razón para hacer esto: el rendimiento. Por eso algunos métodos integrados de listas los cambian, como reverse() o sort():
items = [3, 2, 1, 5, 4]
items.sort()
print(items) # => [1, 2, 3, 4, 5]
items.reverse()
print(items) # => [5, 4, 3, 2, 1]
Copia de listas
Python no cuenta con métodos o funciones integradas que modifiquen una lista y, al mismo tiempo, devuelvan una nueva sin alterar la original. Si deseas modificar una lista sin afectar la original, es necesario hacer una copia de ella.
def append(items, item):
# O items_copy = items[:]
items_copy = items.copy()
items_copy.append(item)
return items_copy
items = [1, 2, 3]
items2 = append(items, 4)
print(items) # => [1, 2, 3]
print(items2) # => [1, 2, 3, 4]
A pesar de que el enfoque que cambia listas directamente es más difícil de depurar, se utiliza en algunos lenguajes para aumentar el rendimiento del trabajo. Si la lista es suficientemente grande, la copia completa resultará en una operación costosa. En la vida real (como desarrollador web), esto casi nunca es un problema, pero es útil saberlo.
Conclusiones
Todos los tipos de datos en Python se pasan por referencias. Las listas contienen una colección no de valores, sino de referencias.
Al pasar una lista a una función se pasa una referencia a ella. Cambiar la lista dentro de la función afectará al original. Es preferible devolver una nueva lista de la función para evitar cambios inesperados.
Para acceder completo a curso necesitas un plan básico
El plan básico te dará acceso completo a todos los cursos, ejercicios y lecciones de Códica, proyectos y acceso de por vida a la teoría de las lecciones completadas. La suscripción se puede cancelar en cualquier momento.