Artículos de Tecnología > Programación

Cómo hacer una copia de una lista en Python

Yan Orestes
Yan Orestes
Imagem de destaque

Mis amigos del club de libros y yo creamos un sistema Python para organizar qué libros tiene cada uno de nosotros.

Cada uno tiene su propia lista, en la que los libros se dividen por categoría (en mi caso, SQL, PHP y Front-end). Cada categoría también es una lista:

libros_yan = [['Banco MySQL'], ['Certificación PHP', 'TDD PHP'], ['HTML5 y CSS3']]

Recientemente, recibimos a un nuevo miembro del club, Pedro, y él decidió comprar una copia de todos los libros que tenía:

libros_pedro = libros_yan

print(libros_pedro)

Y ahora la lista de libros de Pedro:

[['Banco MySQL'], ['Certificação PHP', 'TDD PHP'], ['HTML5 e CSS3']]

¡Todo bien!

Seguimos al club y terminé adquiriendo otro libro en una categoría que aún no tenía - Juegos:

libros_games = ['Juegos iOS']
libros_yan.append(libros_games)

print(libros_yan)

Ahora mira cómo está mi lista:

[['Banco MySQL'], ['Certificação PHP', 'TDD PHP'], ['HTML5 e CSS3'], ['Jogos iOS']]

Bien, ¡como esperábamos! Resulta que Pedro fue a revisar su lista de libros:

print(libros_pedro)

Pero mira cuál fue el resultado:

[['Banco MySQL'], ['Certificación PHP', 'TDD PHP'], ['HTML5 y CSS3'], ['Juegos iOS']]

¡Eh! ¡Pero no había comprado el libro Juegos iOS! Su lista quedó como la lista de mis libros... ¿Por qué?

Listas son objetos mutables

¿Por qué cuando cambiamos mi lista, la lista de Pedro también cambió? Lo que pasa es que no eran listas separadas, eran la misma lista.

Cuando asignamos la referencia de otra variable como el valor de una variable, lo que estamos haciendo es señalar las dos variables, es decir, los dos nombres, al mismo objeto en la memoria de la computadora. Podemos comprobar esto con nuestro conocido operador de identidad is:

libros_pedro = libros_yan

print(libros_pedro is libros_yan)

Y el resultado:

True

Lo mismo ocurre con cualquier otro tipo en Python, como strings:

mi_nombre = ‘Yan’

copia_nombre = mi_nombre
print(mi_nombre is copia_nombre)

El resultado:

True

¿Y qué pasa si intentamos cambiar una de estas variables?

mi_nombre += ' Orestes'
print(mi_nombre)

print(copia_nombre)

Mira el resultado:

Yan Orestes

Yan

¡Solo cambió la variable que pedimos cambiar! Pero bueno, ¿no era el mismo objeto? Era, pero dejó de ser cuando hicimos este cambio. Mira:

mi_nombre = 'Yan'

copia_nombre = mi_nombre
print(‘Antes de la alteración:’)

print(id(mi_nombre))

print(id(copia_nombre))
mi_nombre += 'Orestes'
print(‘Después de la alteración:’)

print(id(mi_nombre))

print(id(copia_nombre))

Y el resultado:

Antes del cambio:

139683901853232

139683901853232

Después del cambio:

139683901897776

139683901853232

Ten en cuenta que el identificador de variable mi_nombre cambió tan pronto cuando cambiamos su valor. Es porque las strings, como la mayoría de los tipos nativos de Python, son inmutables.

"¿Pero cómo inmutables, si acabamos de cambiar el valor de una string?"

Aunque parece que cambiamos la string, de hecho lo que cambiamos fue solo la referencia que se guardaba en el nombre de la variable meu_nome.

No cambiamos el objeto string original, porque no podemos hacer eso, pero creamos otro objeto string con valor Yan Orestes(o meu_nome+ Orestes) y cambiamos la referencia en nuestra variable. Por eso cambió el ID.

A diferencia de las strings, listas son modificables. Es decir, podemos cambiar un objeto lista manteniéndolo en el mismo espacio en la memoria, en lugar de tener que crear otro objeto para reemplazar la referencia de la variable.

Entonces, cuando usamos el método clear(), todavía estamos tratando con el mismo objeto lista con dos identificadores:

libros_yan = [['Base MySQL'], ['Certificación PHP', 'TDD PHP'], ['HTML5 y CSS3']]

libros_pedro = libros_yan

print('Antes del cambio:')

print(id(libros_yan)

print(id(libros_pedro))
libros_games = ['Juegos iOS']

libros_yan.append(libros_games)
print(‘Después del cambio:’)

print(id(libros_yan))

print(id(libros_pedro))

Y el resultado:

Antes del cambio:

139715027862984

139715027862984

Después del cambio:

139715027862984

139715027862984

El identificador de las dos variables siguió siendo el mismo, ya que seguimos tratando el mismo objeto incluso después de su cambio. Entonces, ¿cómo podemos crear un segundo objeto lista igual a uno ya existente?

Haciendo la copia de una lista

Los tipos secuenciales en Python, como las listas, nos brindan una técnica que nos puede ayudar en nuestro objetivo de segmentar o slicing. El slicing nos permite crear otro objeto con sólo un pedazo deseado del objeto original.

La sintaxis de slicing es similar la de una indexación común, pero con un : separando el primer elemento que queremos del último, el último siendo descartado. Con listas, podemos hacer algo como lo siguiente:

>>> libros_yan = [['Base MySQL'], ['Certificación PHP', 'TDD PHP'],... ['HTML5 y CSS3']]

>>> libros_yan[0:2][['Base MySQL'], ['Certificación PHP', 'TDD PHP']]

Asi, podemos tener una copia de la lista en otro objeto:

libros_yan = [[‘Base MySQL’], [‘Certificación PHP’, ‘TDD PHP’], [‘HTML5 y CSS3’]]

libros_pedro = libros_yan[0:3]
print(id(libros_yan))

print(id(libros_pedro))

Y los ID:

139715027878216

139715052415952

Python todavía nos permite omitir el primer número de slicing si queremos tomarlo desde el principio, y el segundo si lo queremos hasta el final, lo que simplifica nuestro código:

libros_pedro = libros_yan[:]

print(libros_pedro)

Y el resultado:

['Base MySQL'], ['Certificación PHP', 'TDD PHP'], ['HTML5 y CSS3']]

¡Los mismos de mi lista!

Ahora intentemos agregar una categoría a mi lista de libros y ver si la de Pedro también cambia:

libros_games = ['Juegos iOS']

libros_yan.append(libros_games)
print(libros_yan)

print()

print(libros_pedro)

Esta vez:

[['Base MySQL'], ['Certificación PHP', 'TDD PHP'], ['HTML5 y CSS3'], ['Juegos iOS']]
[[‘Base MySQL’], [‘Certificación PHP’, ‘TDD PHP’], [‘HTML5 y CSS3’]]

¡Funcionó! También podemos usar el método de lista copy(), que tiene el mismo comportamiento:

libros_pedro = libros_yan.copy()

¡Y todavía funciona de la misma manera!

El problema de las copias superficiales

Todo nuestro sistema estaba funcionando bien, hasta que logré obtener la certificación PHP y decidí donar mi libro sobre el tema a un amigo, creando la necesidad de eliminar este libro de mi lista:

Libros_yan[1].remove('Certificación PHP')
print(libros_yan)

Y ahora mi lista:

[['Base MySQL'], ['TDD PHP'], ['HTML5 y CSS3'], ['Juegos iOS']]

De nuevo Pedro fue a revisar su lista:

print(libros_pedro)

Y el resultado:

[['Banco MySQL'], ['TDD PHP'], ['HTML5 e CSS3']]

¡El libro también desapareció para él! Observa que nuestras listas de libros siguen siendo diferentes, pero cuando eliminé un libro de la categoríaPHP, en su lista también se ha eliminado. ¿Qué pasa?

Cuando usamos el método .copy()o slicing para copiar una lista, estamos haciendo una copia superficial, o técnicamente una _shallow copy_.

En una lista, la copia superficial creará otro objeto lista para almacenar los mismos valores que la primera lista, pero usará los mismos objetos que la primera lista en su interior, como se muestra en la imagen:

Como funciona a cópia rasa

Leyenda figura:libros_yanShallow copy [‘Certificación PHP’, ‘TDD PHP’]list [‘Base MySQL’]list [‘HTML5 y CSS3’]

Incluso podemos comprobar los ID de los miembros de las listas:

libros_yan = [['Base MySQL’], [‘Certificación PHP’, ‘TDD PHP’], 
[‘HTML5 y CSS3’]]
libros_pedro = libros_yan.copy()

print(‘libros_yan - ID: {}’.format(id(libros_yan))
print(‘libros_pedro - ID: {}’.format(id(libros_pedro))
print()
print(‘libros_yan[1] - ID: {}’.format(id(libros_yan[1]))
print(‘libros_pedro[1] - ID: {}’.format(id(libros_pedro[1]))

son_mismo_objeto = libros_yan[1] is libros_pedro[1]
print(‘libros_yan[1] is libros_pedro[1]: {}’.format(son_mismo_objeto))

Y el resultado:

libros_yan - ID: 139747348710920
libros_pedro - ID: 139848030606832

libros_yan[1] - ID: 139747372553152
libros_pedro[1] - ID: 139747372553152
libros_yan[1] is libros_pedro[1]: True

Como vemos, a pesar del método copy() haber creado otro objeto lista para almacenar los valores, todos los elementos de la lista livros_yan han sido reutilizados.

Por tanto, tenemos el mismo problema que al principio, ya que las listas son objetos mutables. Esto es lo que limita las copias superficiales. ¿Cómo podemos superar esto?

Haciendo una copia profunda de una lista

En programación, también tenemos otro concepto de copia, que difiere de la copia superficial: la copia profunda, o deep copy.

A diferencia de la copia superficial, no solo inserta las referencias de los valores de la lista original en la nueva lista, sino que también crea nuevos objetos para cada elemento, de hecho, separa una lista de la otra.

La siguiente imagen complementa la imagen anterior con este nuevo concepto:

Como funciona cópias rasas e profundas (shallow e deep)

Leyenda figura:libros_yanShallow copy [‘Certificación PHP’, ‘TDD PHP’]list [‘Base MySQL’]list [‘HTML5 y CSS3’]list [‘Bae MySQL’] [‘HTML5 y CSS3’] [‘Certificación PHP’, ‘TDD PHP’]

Pero, ¿cómo podemos usar esta técnica en Python? ¿Tenemos que implementar nosotros mismos una función que haga esto de forma recursiva? Parece laborioso ...

Debido a este problema recurrente de los objetos compuestos, como en nuestro caso de listas que incluyen otras listas, Python viene de forma nativa con una solución en su biblioteca estándar, con el módulo copy.

Dentro de este módulo, tenemos una función específica para esto: deepcopy(). Importándola, su uso es objetivo:

from copy import deepcopy
libros_yan = [[‘Base MySQL’], [‘Certificación PHP’, ‘TDD PHP’], [‘HTML5 y CSS3’]]
libros_pedro = deepcopy(libros_yan)

Ahora intentemos modificar la lista libros_yan:

libros_games = [‘Juegos iOS’]
libros_yan.append(libros_games)

libros_yan[1].remove(‘Certificación PHP’)

print(libros_yan)
print(libros_pedro)

Esta vez: [['Base MySQL'], ['TDD PHP'], ['HTML5 y CSS3'], ['Juegos iOS']][['Base MySQL'], ['Certificación PHP', 'TDD PHP'], ['HTML5 y CSS3']]

¡Correcto! ¡Ahora funcionó exactamente como queríamos!

Conclusión

En este artículo, pudimos comprender la diferencia entre objetos mutables e inmutables en Python, con ejemplos que usan listas. Vimos, entonces, una forma de no tantear con el método copy()o slicing de lista.

Entonces, aprendimos sobre la copia superficial y sus posibles problemas con los objetos compuestos, y cómo resolverlo con copias profundas.

Ahora sabemos cuándo es suficiente una copia superficial, como en listas comunes, y cuándo necesitamos copias profundas, como en listas que contienen otras listas.

Entonces, ¿te gustó el contenido? Si deseas conocer otras funciones y peculiaridades de listas en Python, echa un vistazo a nuestros artículos sobre agregar nuevos elementos a una lista, ordenar una lista, comprensiones de lista y otras herramientas básicas de este tipo.

Si quieres seguir estudiando Python, ¡tenemos algunos cursos en Alura sobre el lenguaje para que sigas adelante con tu aprendizaje!

Puedes leer también:

Artículos de Tecnología > Programación

En Alura encontrarás variados cursos sobre Programación. ¡Comienza ahora!

Precios en:
USD
  • USD
  • BOB
  • CLP
  • COP
  • USD
  • PEN
  • MXN
  • UYU

Semestral

  • 270 cursos

    Cursos de Programación, Front End, Data Science, Innovación y Gestión.

  • Videos y actividades 100% en Español
  • Certificado de participación
  • Estudia las 24 horas, los 7 días de la semana
  • Foro y comunidad exclusiva para resolver tus dudas
  • Luri, la inteligencia artificial de Alura

    Luri es nuestra inteligencia artificial que resuelve dudas, da ejemplos prácticos y ayuda a profundizar aún más durante las clases. Puedes conversar con Luri hasta 100 mensajes por semana

  • Acceso a todo el contenido de la plataforma por 6 meses
US$ 65.90
un solo pago de US$ 65.90
¡QUIERO EMPEZAR A ESTUDIAR!

Paga en moneda local en los siguientes países

Anual

  • 270 cursos

    Cursos de Programación, Front End, Data Science, Innovación y Gestión.

  • Videos y actividades 100% en Español
  • Certificado de participación
  • Estudia las 24 horas, los 7 días de la semana
  • Foro y comunidad exclusiva para resolver tus dudas
  • Luri, la inteligencia artificial de Alura

    Luri es nuestra inteligencia artificial que resuelve dudas, da ejemplos prácticos y ayuda a profundizar aún más durante las clases. Puedes conversar con Luri hasta 100 mensajes por semana

  • Acceso a todo el contenido de la plataforma por 12 meses
US$ 99.90
un solo pago de US$ 99.90
¡QUIERO EMPEZAR A ESTUDIAR!

Paga en moneda local en los siguientes países

Acceso a todos
los cursos

Estudia las 24 horas,
dónde y cuándo quieras

Nuevos cursos
cada semana