Pular para o conteúdo
Editor

Copy e deepcopy em Python: por que sua "cópia" não é uma cópia

Intermediário20 min5 exercícios70 XP

Este tutorial foi traduzido do original em inglês.Read in EnglishLeer en españolЧитать на русскомอ่านภาษาไทยAvisar sobre um erro de tradução

0/5 exercícios

Imagine que você tem uma pasta cheia de fotos. Você faz uma cópia da pasta e entrega para um amigo. Mas tem um detalhe: só a pasta é nova. As fotos lá dentro são as mesmas fotos físicas, compartilhadas pelas duas pastas. Se o seu amigo desenhar um bigode em uma delas, a sua foto também fica com o bigode.

É exatamente assim que a cópia padrão do Python funciona. Você acha que fez uma cópia independente, mas os objetos internos continuam compartilhados. É um dos bugs mais confusos do Python, e ele pega tanto iniciantes quanto desenvolvedores experientes.

Neste tutorial, você vai aprender a diferença entre atribuição, cópia rasa e cópia profunda. Ao final, você vai saber exatamente quando usar cada uma e nunca mais vai cair em um bug de "referência compartilhada".

Por que mudar uma "cópia" muda o original?

Vamos começar pela surpresa mais comum. Quando você escreve b = a, você NÃO está fazendo uma cópia. Está criando um segundo nome para exatamente o mesmo objeto. Tanto a quanto b apontam para os mesmos dados na memória.

A atribuição cria um apelido, não uma cópia
a = [1, 2, 3]
b = a # NOT a copy - just a second name
b.append(4)
print(a) # [1, 2, 3, 4] - a changed too!
print(b) # [1, 2, 3, 4]
Saída
[1, 2, 3, 4]
[1, 2, 3, 4]

Isso acontece porque a e b são duas etiquetas coladas na mesma caixa. Quando você coloca algo novo na caixa usando a etiqueta b, também vê essa mudança ao olhar pela etiqueta a.

Você pode confirmar que duas variáveis apontam para o mesmo objeto usando o operador is ou comparando o id() delas.

Verificando a identidade de objetos com is e id()
a = [1, 2, 3]
b = a
print(a is b) # True - same object
print(id(a)) # Same number...
print(id(b)) # ...as this one
Saída
True
80036304
80036304

O que é uma cópia rasa?

Uma cópia rasa (shallow copy) cria um novo contêiner externo, mas NÃO copia os objetos que estão dentro dele. A nova lista é independente no nível de cima, mas os objetos aninhados continuam compartilhados com o original.

Existem várias formas de fazer uma cópia rasa de uma lista.

Três formas de fazer uma cópia rasa de uma lista
original = [1, 2, 3]
# Three ways to shallow copy a list
copy1 = original.copy() # .copy() method
copy2 = original[:] # slice from start to end
copy3 = list(original) # list() constructor
# They are all independent copies
copy1.append(99)
print(original) # [1, 2, 3] - unchanged!
print(copy1) # [1, 2, 3, 99]
Saída
[1, 2, 3]
[1, 2, 3, 99]

Para dicionários, use .copy() ou dict().

Cópia rasa de um dicionário
original = {'name': 'Alice', 'age': 25}
copy1 = original.copy() # .copy() method
copy2 = dict(original) # dict() constructor
copy1['age'] = 30
print(original) # {'name': 'Alice', 'age': 25} - unchanged
print(copy1) # {'name': 'Alice', 'age': 30}
Saída
{'name': 'Alice', 'age': 25}
{'name': 'Alice', 'age': 30}

Parece perfeito. Mas aqui está a armadilha: a cópia rasa só copia um nível. Se a sua lista contém outras listas (ou dicionários), esses objetos internos continuam compartilhados.

A armadilha da cópia rasa com dados aninhados
# Shallow copy with nested data - the trap!
original = [[1, 2], [3, 4], [5, 6]]
shallow = original.copy()
# Modifying the outer list is fine
shallow.append([7, 8])
print(len(original)) # 3 - unaffected
print(len(shallow)) # 4 - only shallow changed
# But modifying an INNER list affects both!
shallow[0].append(99)
print(original[0]) # [1, 2, 99] - original changed too!
print(shallow[0]) # [1, 2, 99]
Saída
3
4
[1, 2, 99]
[1, 2, 99]
Cópia rasa: listas internas compartilhadas
original = [[1, 2], [3, 4]]
shallow = original.copy()
shallow[0].append(99)
print(original)  # [[1, 2, 99], [3, 4]]
Cópia profunda: tudo independente
import copy
original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)
deep[0].append(99)
print(original)  # [[1, 2], [3, 4]]

O que é uma cópia profunda e quando você precisa dela?

Uma cópia profunda (deep copy) cria um clone totalmente independente. Ela copia o objeto externo E copia, de forma recursiva, cada objeto mutável dentro dele, não importa quantos níveis de aninhamento existam. A cópia não compartilha com o original nada que possa ser alterado: só os valores que não mudam, como números e strings, são reaproveitados.

Para fazer uma cópia profunda, você precisa do módulo copy da biblioteca padrão do Python.

A cópia profunda cria um clone totalmente independente
import copy
original = [[1, 2], [3, 4], [5, 6]]
deep = copy.deepcopy(original)
# Modify the deep copy
deep[0].append(99)
deep.append([7, 8])
print(original) # [[1, 2], [3, 4], [5, 6]] - completely unchanged!
print(deep) # [[1, 2, 99], [3, 4], [5, 6], [7, 8]]
Saída
[[1, 2], [3, 4], [5, 6]]
[[1, 2, 99], [3, 4], [5, 6], [7, 8]]

A cópia profunda também funciona com dicionários, inclusive em estruturas com muitos níveis de aninhamento.

Cópia profunda com dicionários e listas aninhados
import copy
user = {
'name': 'Alice',
'scores': [90, 85, 92],
'profile': {'level': 5, 'badges': ['star', 'gold']},
}
clone = copy.deepcopy(user)
# Modify the clone at every level
clone['name'] = 'Bob'
clone['scores'].append(100)
clone['profile']['badges'].append('diamond')
print(user['name']) # Alice
print(user['scores']) # [90, 85, 92]
print(user['profile']['badges']) # ['star', 'gold']
print(clone['name']) # Bob
print(clone['scores']) # [90, 85, 92, 100]
print(clone['profile']['badges']) # ['star', 'gold', 'diamond']
Saída
Alice
[90, 85, 92]
['star', 'gold']
Bob
[90, 85, 92, 100]
['star', 'gold', 'diamond']

Por dentro: como as referências funcionam em Python

Para entender de verdade como as cópias funcionam, você precisa entender como o Python guarda os dados. Cada dado vive em algum lugar da memória como um objeto. Variáveis são apenas nomes que referenciam (apontam para) esses objetos.

A atribuição copia a referência. A cópia rasa cria um novo contêiner com as mesmas referências dentro. A cópia profunda cria um novo contêiner cujas referências apontam para novas cópias de cada objeto mutável que está dentro.

Visualizando referências com is
import copy
inner = [1, 2, 3]
original = [inner, inner] # Both slots point to the same inner list
shallow = copy.copy(original)
deep = copy.deepcopy(original)
print(original[0] is original[1]) # True - same inner list
print(shallow[0] is original[0]) # True - shallow shares inner objects
print(deep[0] is original[0]) # False - deep made a new copy
Saída
True
True
False

Aqui está uma tabela que resume as três abordagens.

Resumo: atribuição, cópia rasa e cópia profunda
# Summary:
# ─────────────────────────────────────────────────────
# Method | New outer? | New inner? | Safe for nested?
# ─────────────────────────────────────────────────────
# b = a | No | No | No
# b = a.copy() | Yes | No | No
# b = deepcopy(a) | Yes | Yes | Yes
# ─────────────────────────────────────────────────────
print("Assignment: same outer, same inner")
print("Shallow copy: new outer, same inner")
print("Deep copy: new outer, new inner")
Saída
Assignment:   same outer, same inner
Shallow copy: new outer, same inner
Deep copy:    new outer, new inner

Quando usar cada tipo de cópia?

Use a atribuição (b = a) quando você quer, de propósito, dois nomes para os mesmos dados. Isso é comum ao passar dados para funções: normalmente você quer que a função trabalhe com o original.

Use a cópia rasa quando você tem uma lista ou um dicionário plano (sem objetos mutáveis aninhados) e precisa de uma cópia independente. Isso cobre a maioria dos casos do dia a dia.

Use a cópia profunda quando você tem objetos mutáveis aninhados (listas de listas, dicionários com listas como valores etc.) e precisa de um clone totalmente independente. Isso é essencial quando você quer modificar a cópia sem nenhum risco de afetar o original.


Exercícios práticos

Preveja a saída: apelidos na atribuição
Preveja a saída

Leia o código com atenção. O que ele vai exibir? Lembre-se de que = com listas cria um apelido, não uma cópia.

Digite a saída exata.

a = [10, 20, 30]
b = a
b[1] = 99
print(a)
print(a is b)
Faça uma cópia independente
Corrija o erro

O código abaixo tenta criar um backup da lista original antes de modificá-la. Mas o backup também muda! Corrija o código para que o backup fique inalterado.

Saída esperada:

Original: [1, 2, 3, 4]
Backup: [1, 2, 3]
original = [1, 2, 3]
backup = original
original.append(4)
print(f"Original: {original}")
print(f"Backup: {backup}")
Preveja a saída: a armadilha da cópia rasa
Preveja a saída

Este código usa uma cópia rasa em dados aninhados. Leia com atenção e preveja o que será exibido.

Digite a saída exata.

original = [[1, 2], [3, 4]]
shallow = original.copy()
shallow[0].append(5)
print(original)
print(shallow)
Cópia segura de dados aninhados
Escreva o código

Crie uma cópia totalmente independente dos dados de original usando cópia profunda. Depois, adicione a nota 100 à lista de notas da cópia. Exiba as notas do original e as notas da cópia em linhas separadas, para provar que elas são independentes.

original = {
'name': 'Alice',
'scores': [85, 90, 78],
}
# Create a deep copy
# Add 100 to the copy's scores
# Print original scores, then copy's scores
Corrija o bug do estado do jogo
Corrija o erro

Este jogo salva um checkpoint do estado do jogador e, em seguida, o jogador pega um item. Mas, quando carregamos o checkpoint, o item já está lá! Corrija o código para que o checkpoint seja um retrato realmente independente.

Saída esperada:

Checkpoint inventory: ['sword', 'shield']
Current inventory: ['sword', 'shield', 'potion']
player = {
'name': 'Hero',
'hp': 100,
'inventory': ['sword', 'shield'],
}
# Save checkpoint
checkpoint = player.copy()
# Player picks up a potion
player['inventory'].append('potion')
print(f"Checkpoint inventory: {checkpoint['inventory']}")
print(f"Current inventory: {player['inventory']}")

Tutoriais relacionados

Este tutorial foi útil?