Como fazer uma cópia de uma lista no Python

Como fazer uma cópia de uma lista no Python
Imagem de destaque #cover

Apesar de parecer uma tarefa trivial, tentativas aparentemente simples e óbvias podem ter resultados inesperados, visto que listas, no Python, são objetos mutáveis. Mas o que significa tudo isso?

Eu e meus amigos do clube de livros criamos um sistema em Python para organizar que livros cada um de nós tem.

Cada um tem sua própria lista, na qual os livros são divididos por categoria (no meu caso, SQL, PHP e Front-end). Cada categoria também é uma lista:


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

Recentemente, recebemos um participante novo do clube, Pedro, e ele decidiu comprar uma cópia de todos os livros que eu tinha:


livros_pedro = livros_yan
print(livros_pedro)

E agora a lista de livros do Pedro:


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

Tudo certo!

Seguimos o clube e eu acabei adquirindo outro livro de uma categoria que eu ainda não tinha - Games:


livros_games = ['Jogos iOS']

livros_yan.append(livros_games)
print(livros_yan)

Agora olha como está minha lista:


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

Certo, como esperávamos! Acontece que o Pedro foi checar a lista de livros dele:


print(livros_pedro)

Mas olha qual foi o resultado:


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

Ué! Mas ele não tinha comprado o livro Jogos iOS! A lista dele ficou como a lista dos meus livros… Por que será?

Listas são objetos mutáveis

Por que quando alteramos a minha lista, a lista do Pedro foi alterada junto? O que acontece é que não eram listas separadas, eram a mesma lista.

Quando atribuímos como valor de uma variável a referência de uma outra, o que estamos fazendo é apontar as duas variáveis, ou seja, os dois nomes, para o mesmo objeto na memória do computador. Podemos comprovar isso com nosso conhecido operador de identidade is:


livros_pedro = livros_yan
print(livros_pedro is livros_yan)

E o resultado:


True

O mesmo acontece com qualquer outro tipo no Python, como strings:


meu_nome = ‘Yan’
copia_nome = meu_nome

print(meu_nome is copia_nome)

O resultado:


True

E o que acontece se tentarmos alterar uma dessas variáveis?


meu_nome += ' Orestes'

print(meu_nome)
print(copia_nome)

Olha o resultado:


Yan Orestes
Yan

Só alterou a variável que pedimos para alterar! Mas ué, não era o mesmo objeto? Era, mas deixou de ser quando fizemos essa alteração. Olha só:


meu_nome = 'Yan'
copia_nome = meu_nome

print(‘Antes da alteração:’)
print(id(meu_nome))
print(id(copia_nome))

meu_nome += 'Orestes'

print(‘Depois da alteração:’)
print(id(meu_nome))
print(id(copia_nome))

E o resultado:


Antes da alteração:
139683901853232
139683901853232
Depois da alteração:
139683901897776
139683901853232

Note que o identificador da variável meu_nome mudou assim que alteramos seu valor. Isso é porque strings, assim como grande parte dos tipos nativos do Python, são imutáveis.

"Mas como imutáveis, se acabamos de mudar o valor de uma string?"

Apesar de parecer que alteramos a string, na verdade o que alteramos foi apenas a referência que era guardada no nome de variável meu_nome.

Não alteramos o objeto string original, porque não podemos fazer isso, mas criamos outro objeto string com valor Yan Orestes (ou meu_nome + ‘ Orestes’) e mudamos a referência em nossa variável. Por isso, o ID mudou.

Diferente das string, listas são mutáveis. Ou seja, podemos alterar um objeto lista mantendo ele no mesmo espaço na memória, em vez de termos de criar outro objeto para substituir a referência da variável.

Assim, quando usamos o método clear(), ainda estamos tratando do mesmo objeto lista com os dois identificadores:


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

print('Antes da alteração:')
print(id(livros_yan)
print(id(livros_pedro))

livros_games = ['Jogos iOS']
livros_yan.append(livros_games)

print(‘Depois da alteração:’)
print(id(livros_yan))
print(id(livros_pedro))

E o resultado:


Antes da alteração:
139715027862984
139715027862984
Depois da alteração:
139715027862984
139715027862984

O identificador das duas variáveis se manteve o mesmo, pois continuamos tratamos do mesmo objeto mesmo depois de sua alteração. Como, então, podemos criar um segundo objeto lista igual a um já existente?

Banner da Escola de Programação: Matricula-se na escola de Programação. Junte-se a uma comunidade de mais de 500 mil estudantes. Na Alura você tem acesso a todos os cursos em uma única assinatura; tem novos lançamentos a cada semana; desafios práticos. Clique e saiba mais!

Fazendo a cópia de uma lista

Os tipos sequenciais no Python, como as lista, nos disponibilizam uma técnica que pode nos ajudar em nosso objetivo - o fatiamento, ou slicing. O slicing nos permite criar um outro objeto apenas com um pedaço desejado do objeto original.

A sintaxe do slicing é parecida com a de uma indexação comum, mas com um : separando o primeiro elemento que queremos do último, sendo o último descartado. Com listas, podemos fazer algo como o seguinte:


>>> livros_yan = [['Banco MySQL'], ['Certificação PHP', 'TDD PHP'],
... ['HTML5 e CSS3']]
>>> livros_yan[0:2]
[['Banco MySQL'], ['Certificação PHP', 'TDD PHP']]

Assim, podemos ter uma cópia da lista em outro objeto:


livros_yan = [[‘Banco MySQL’], [‘Certificação PHP’, ‘TDD PHP’], [‘HTML5 e CSS3’]]
livros_pedro = livros_yan[0:3]

print(id(livros_yan))
print(id(livros_pedro))

E os IDs:


139715027878216
139715052415952

O Python ainda nos permite omitir o primeiro número do slicing se quisermos pegar desde o começo, e o segundo se quisermos até o final, o que simplifica nosso código:


livros_pedro = livros_yan[:]
print(livros_pedro)

E o resultado:


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

Os mesmos da minha lista!

Agora vamos tentar adicionar uma categoria à minha lista de livros e ver se a do Pedro muda também:


livros_games = ['Jogos iOS']
livros_yan.append(livros_games)

print(livros_yan)
print()
print(livros_pedro)

Dessa vez:


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

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

Deu certo! Também podemos usar o método de lista copy(), que tem o mesmo comportamento:


livros_pedro = livros_yan.copy()

E continua funcionando da mesma maneira!

O problema das cópias rasas

Todo o nosso sistema estava funcionando bem, até que eu consegui tirar a certificação PHP e decidi doar meu livro sobre o assunto para um amigo, criando a necessidade de remover esse livro de minha lista:


livros_yan[1].remove('Certificação PHP')

print(livros_yan)

E agora, minha lista:


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

De novo, o Pedro foi checar a lista dele:


print(livros_pedro)

E o resultado:


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

O livro sumiu para ele também! Repare que nossas listas de livros ainda estão diferentes, mas quando eu removi um livro da categoria PHP, na lista dele também foi removido. O que acontece?

Quando usamos o método .copy() ou o slicing para copiar uma lista, estamos fazendo uma cópia rasa, ou, tecnicamente, uma _shallow copy_.

Em uma lista, a cópia rasa vai criar outro objeto lista para armazenar os mesmos valores da primeira lista, mas vai usar os mesmos objetos que a primeira lista em seu interior, como indica a imagem:

Como funciona a cópia rasa

Podemos até checar os IDs dos membros das listas:


livros_yan = [['Banco MySQL’], [‘Certificação PHP’, ‘TDD PHP’], [‘HTML5 e CSS3’]]
livros_pedro = livros_yan.copy()

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

sao_mesmo_objeto = livros_yan[1] is livros_pedro[1]
print(‘livros_yan[1] is livros_pedro[1]: {}’.format(sao_mesmo_objeto))

E o resultado:


livros_yan - ID: 139747348710920
livros_pedro - ID: 139848030606832

livros_yan[1] - ID: 139747372553152
livros_pedro[1] - ID: 139747372553152
livros_yan[1] is livros_pedro[1]: True

Como podemos ver, apesar do método copy() ter criado um outro objeto lista para armazenar os valores, todos os elementos da lista livros_yan foram reutilizados.

Dessa forma, temos o mesmo problema do começo, já que listas são objetos mutáveis. Isso é o que limita as cópias rasas. Como podemos superar isso?

Fazendo uma cópia profunda de uma lista

Em programação, ainda temos outro conceito de cópia, que se diferencia da cópia rasa - a cópia profunda, ou deep copy.

Diferentemente da cópia rasa, ela não simplesmente insere as referências dos valores da lista original dentro a nova lista, mas também cria novos objetos para cada elemento, de fato separando uma lista da outra.

A imagem abaixo complementa a imagem anterior com esse novo conceito:

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

Mas como podemos usar esta técnica no Python? Será que teremos que implementar nós mesmos uma função que faça isso recursivamente? Parece trabalhoso…

Por conta desse problema recorrente de objetos compostos, como nosso caso de listas que incluem outras listas, o Python já vem nativamente com uma solução em sua biblioteca padrão - com o módulo copy.

Dentro desse módulo, temos uma função específica para isso - a deepcopy(). Importando-a, seu uso é objetivo:


from copy import deepcopy

livros_yan = [[‘Banco MySQL’], [‘Certificação PHP’, ‘TDD PHP’], [‘HTML5 e CSS3’]]
livros_pedro = deepcopy(livros_yan)

Agora vamos tentar modificar a lista livros_yan:


livros_games = [‘Jogos iOS’]
livros_yan.append(livros_games)

livros_yan[1].remove(‘Certificação PHP’)

print(livros_yan)
print(livros_pedro)

Dessa vez:


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

Certo! Agora sim funcionou exatamente como queríamos!

Conclusão

Nesse artigo, pudemos entender a diferença de objetos mutáveis e imutáveis no Python, com exemplos usando listas. Vimos, então, uma forma de não nos atrapalharmos com isso com o método copy() ou o slicing de lista.

Assim, aprendemos sobre cópia rasa e seus possíveis problemas para com objetos compostos, e como resolver isso com cópias profundas.

Agora sabemos quando basta uma cópia rasa, como em listas comuns, e quando precisamos de cópias profundas, como em listas que contêm outras listas.

E aí, gostou do conteúdo? Se quiser conhecer outras funcionalidades e peculiaridades de listas no Python, dê uma olhada em nossos artigos sobre adicionar novos elementos em uma lista, ordenar uma lista, compreensões de lista e outras ferramentas básicas desse tipo.

Se quiser continuar estudando Python, temos alguns cursos na Alura sobre a linguagem para você seguir em frente com seu aprendizado!

Veja outros artigos sobre Programação