Python Cópia Rasa e Cópia Profunda
Na aula Mutável × Imutável, você viu que b = a não copia nada: só cola mais uma etiqueta no mesmo objeto. E viu que compras.copy() resolve. Agora a professora da turma da Ana tem uma lista em que cada aluno é outra lista, com o nome e as notas. Ela faz uma cópia para testar a nota da recuperação e descobre que as notas originais mudaram também. Nesta aula você vai entender por quê: existem dois tipos de cópia, a rasa e a profunda, e a diferença só aparece quando há objetos dentro de objetos.
- O que é uma cópia rasa (
copy.copy,.copy(),list(),[:]) e o que ela copia de verdade. - Por que mudar uma nota na cópia rasa muda a nota original.
- O que é uma cópia profunda (
copy.deepcopy) e quando ela é necessária. - Como conferir com
iso que foi copiado e o que é compartilhado. - Cópias de dicionários e de objetos das suas classes.
- Quando a cópia rasa basta (e por que ela é a mais usada).
- A armadilha do
[[0] * 3] * 2e os erros reais.
A ideia: a pasta e as folhas
A professora guarda as notas numa pasta, com uma folha para cada aluno. Ela pede duas cópias na secretaria:
- Na cópia rasa, a secretaria compra uma pasta nova e põe dentro… as mesmas folhas, com um bilhete "a folha da Bia está lá na outra pasta". A pasta é nova: dá para pôr ou tirar folhas sem mexer na original. Mas, se alguém riscar uma nota na folha da Bia, risca a única folha da Bia que existe.
- Na cópia profunda, a secretaria compra uma pasta nova e tira xerox de cada folha. Agora dá para riscar à vontade: as folhas originais estão intactas.
Em Python, a pasta é a lista de fora (turma) e as folhas são as listas de dentro (uma por aluno).
turma ──► [ ● , ● ] a pasta (lista de fora)
│ └──► ["Caio", 6.0, 5.5] uma folha (lista de dentro)
└──────► ["Bia", 8.5, 7.0] outra folha
cópia rasa: pasta NOVA, apontando para as MESMAS folhas
cópia profunda: pasta NOVA, com folhas NOVAS (xerox de cada uma)
1. Lembrando: = não é cópia
turma = [["Bia", 8.5, 7.0], ["Caio", 6.0, 5.5]] outra = turma outra.append(["Duda", 9.0, 9.5]) print(len(turma)) print(outra is turma)
$ python3 igual_nao_copia.py 3 True
A Duda entrou na turma também: outra e turma são duas etiquetas na mesma pasta (is dá True). Nenhuma cópia foi feita.
2. A cópia rasa: copy.copy
O módulo copy ("copiar"), que já vem com o Python, tem as duas cópias. A rasa se chama copy.copy:
import copy
turma = [["Bia", 8.5, 7.0], ["Caio", 6.0, 5.5]]
rasa = copy.copy(turma)
print(rasa == turma, rasa is turma)
rasa.append(["Duda", 9.0, 9.5])
print("Original:", turma)
print("Cópia: ", rasa)
- import copy
- Traz o módulo
copy(aula Módulos e import). - copy.copy(turma)
- Cria uma lista nova (a pasta nova), com os mesmos itens da
turma. - rasa == turma, rasa is turma
- Mesmo conteúdo (
True), mas objetos diferentes (False): agora existem duas pastas. - rasa.append(…)
- Põe uma folha nova só na pasta nova.
$ python3 rasa.py True False Original: [['Bia', 8.5, 7.0], ['Caio', 6.0, 5.5]] Cópia: [['Bia', 8.5, 7.0], ['Caio', 6.0, 5.5], ['Duda', 9.0, 9.5]]
Funcionou: a Duda entrou só na cópia. Para pôr e tirar itens da lista de fora, a cópia rasa é suficiente.
Missão: a professora quer uma turma nova, com a Duda, sem mexer na turma original. Troque o = por uma cópia (rasa basta aqui) para que o original continue com 2 alunos.
turma = [["Bia", 8.5, 7.0], ["Caio", 6.0, 5.5]]
nova_turma = turma
nova_turma.append(["Duda", 9.0, 9.5])
print("Original:", len(turma), "alunos")
print("Cópia:", len(nova_turma), "alunos")
import copy
turma = [["Bia", 8.5, 7.0], ["Caio", 6.0, 5.5]]
nova_turma = copy.copy(turma)
nova_turma.append(["Duda", 9.0, 9.5])
print("Original:", len(turma), "alunos")
print("Cópia:", len(nova_turma), "alunos")
3. A surpresa: mudar uma nota na cópia rasa
Agora a professora quer testar na cópia uma nota 10 para a Bia:
import copy
turma = [["Bia", 8.5, 7.0], ["Caio", 6.0, 5.5]]
rasa = copy.copy(turma)
rasa[0][1] = 10.0
print("Original:", turma)
print("Cópia: ", rasa)
print(rasa is turma)
print(rasa[0] is turma[0])
$ python3 rasa_surpresa.py Original: [['Bia', 10.0, 7.0], ['Caio', 6.0, 5.5]] Cópia: [['Bia', 10.0, 7.0], ['Caio', 6.0, 5.5]] False True
A nota mudou no original. As duas últimas linhas explicam:
rasa is turmaéFalse: as pastas são diferentes.rasa[0] is turma[0]éTrue: a folha da Bia é a mesma nas duas pastas.
rasa[0][1] = 10.0 quer dizer "na folha da Bia (rasa[0]), troque a primeira nota ([1])". Como só existe uma folha da Bia, a mudança aparece pelas duas pastas. É isso que "rasa" quer dizer: a cópia vai só até a primeira camada. Daí para dentro, tudo é compartilhado.
4. A cópia profunda: copy.deepcopy
import copy
turma = [["Bia", 8.5, 7.0], ["Caio", 6.0, 5.5]]
profunda = copy.deepcopy(turma)
profunda[0][1] = 10.0
print("Original:", turma)
print("Cópia: ", profunda)
print(profunda is turma)
print(profunda[0] is turma[0])
- copy.deepcopy(turma)
- Deep copy, "cópia profunda". Cria uma lista nova e uma cópia nova de cada lista lá dentro, e assim por diante, em todas as camadas: a xerox de cada folha.
$ python3 profunda.py Original: [['Bia', 8.5, 7.0], ['Caio', 6.0, 5.5]] Cópia: [['Bia', 10.0, 7.0], ['Caio', 6.0, 5.5]] False False
Agora o original ficou intacto, e profunda[0] is turma[0] deu False: a folha da Bia na cópia é outra folha. A cópia profunda é totalmente independente do original.
Missão: a professora soma 1 ponto em cada nota da cópia, para simular a recuperação, mas as notas originais estão mudando também (rode e veja). Troque o tipo de cópia para que o original fique intacto.
import copy
turma = [["Bia", 8.5, 7.0], ["Caio", 6.0, 5.5]]
recuperacao = copy.copy(turma)
for aluno in recuperacao:
aluno[1] += 1
aluno[2] += 1
print("Original:", turma)
print("Recuperação:", recuperacao)
import copy
turma = [["Bia", 8.5, 7.0], ["Caio", 6.0, 5.5]]
recuperacao = copy.deepcopy(turma)
for aluno in recuperacao:
aluno[1] += 1
aluno[2] += 1
print("Original:", turma)
print("Recuperação:", recuperacao)
5. Os vários jeitos de fazer uma cópia rasa
Você não precisa importar nada para a cópia rasa de uma lista. Estes quatro jeitos fazem exatamente a mesma coisa:
import copy
turma = [["Bia", 8.5, 7.0], ["Caio", 6.0, 5.5]]
copias = [turma.copy(), list(turma), turma[:], copy.copy(turma)]
for c in copias:
print(c is turma, c[0] is turma[0])
$ python3 formas_rasas.py False True False True False True False True
Nos quatro casos, a pasta é nova (False) e a folha da Bia é a mesma (True): todas são cópias rasas. O turma[:] é a fatia "do começo ao fim" (aula Índices e Fatias), e o list(turma) cria uma lista a partir de outra.
Também dá para copiar as duas camadas "à mão", copiando cada lista de dentro com uma list comprehension (aula List Comprehension):
turma = [["Bia", 8.5, 7.0], ["Caio", 6.0, 5.5]]
copia = [aluno.copy() for aluno in turma]
copia[0][1] = 10.0
print("Original:", turma)
print("Cópia: ", copia)
$ python3 manual.py Original: [['Bia', 8.5, 7.0], ['Caio', 6.0, 5.5]] Cópia: [['Bia', 10.0, 7.0], ['Caio', 6.0, 5.5]]
Funciona porque aqui são exatamente duas camadas. Se houvesse uma terceira (uma lista dentro da lista do aluno), ela continuaria compartilhada. O deepcopy desce até o fim, quantas camadas houver.
6. Dicionários: a mesma regra
O .copy() de um dicionário também é raso. O cardápio da lojinha tem uma lista de itens para cada categoria:
import copy
cardapio = {"cafés": ["expresso", "coado"], "doces": ["bolo"]}
rasa = cardapio.copy()
profunda = copy.deepcopy(cardapio)
rasa["salgados"] = ["coxinha"]
rasa["cafés"].append("cappuccino")
profunda["doces"].append("brigadeiro")
print("Original:", cardapio)
print("Rasa: ", rasa)
print("Profunda:", profunda)
$ python3 dicionario.py
Original: {'cafés': ['expresso', 'coado', 'cappuccino'], 'doces': ['bolo']}
Rasa: {'cafés': ['expresso', 'coado', 'cappuccino'], 'doces': ['bolo'], 'salgados': ['coxinha']}
Profunda: {'cafés': ['expresso', 'coado'], 'doces': ['bolo', 'brigadeiro']}
- A chave nova
"salgados"ficou só na cópia rasa: acrescentar chaves é mexer na primeira camada. - O
"cappuccino"apareceu no original: a lista de cafés é a mesma nas duas. - O
"brigadeiro"ficou só na profunda: ela tem as suas próprias listas.
7. Objetos das suas classes
O módulo copy também copia objetos das classes que você escreve, com a mesma regra. Uma Turma feita com dataclass (aula Dataclasses):
import copy
from dataclasses import dataclass, field
@dataclass
class Turma:
nome: str
alunos: list = field(default_factory=list)
manha = Turma("Manhã", ["Bia", "Caio"])
rasa = copy.copy(manha)
profunda = copy.deepcopy(manha)
manha.alunos.append("Duda")
print(rasa)
print(profunda)
$ python3 objetos.py Turma(nome='Manhã', alunos=['Bia', 'Caio', 'Duda']) Turma(nome='Manhã', alunos=['Bia', 'Caio'])
A cópia rasa é um objeto Turma novo, mas o atributo alunos aponta para a mesma lista; por isso a Duda apareceu nela. A profunda copiou a lista também.
8. Quando a cópia rasa basta
Se os itens de dentro são imutáveis (textos, números, tuplas de imutáveis), compartilhá-los não tem perigo nenhum: ninguém consegue mudá-los. Você só consegue trocar o item da cópia por outro, e isso é mexer na primeira camada:
import copy compras = ["café", "leite"] copia = compras.copy() copia[0] = "chá" print(compras, copia) print(copia[1] is compras[1]) ponto = (2, 3) print(copy.copy(ponto) is ponto) print(copy.deepcopy(ponto) is ponto)
$ python3 imutaveis.py ['café', 'leite'] ['chá', 'leite'] True True True
- Trocar
copia[0]não afetou a lista original, mesmo com a cópia rasa. - O texto
"leite"é o mesmo objeto nas duas listas (True), e tudo bem: ele nunca vai mudar. - O Python é esperto com imutáveis: copiar a tupla
(2, 3), rasa ou profunda, devolve a própria tupla. Para que gastar memória com uma cópia de algo que não pode mudar?
| O que tem dentro | Cópia rasa basta? |
|---|---|
Textos, números, True/False, None | Sim |
| Tuplas só de imutáveis | Sim |
| Listas, dicionários, conjuntos, objetos que você vai modificar | Não: use copy.deepcopy (ou copie cada camada) |
| Listas de dentro que você só vai ler, nunca mudar | Sim (mas cuidado se o programa crescer) |
Formas diferentes de fazer a mesma coisa
| Escrita | O que é | Quando usar |
|---|---|---|
b = a | Nenhuma cópia: mais uma etiqueta | Quando você quer mesmo o mesmo objeto |
a.copy() | Cópia rasa (listas, dicionários, conjuntos) | O jeito mais claro para a cópia rasa de uma lista ou dicionário |
list(a), dict(a), a[:] | Cópia rasa | Também comuns; list(x) serve para copiar de qualquer coisa que se percorre |
copy.copy(a) | Cópia rasa de qualquer objeto | Objetos que não têm .copy() (os das suas classes) |
[x.copy() for x in a] | Cópia de duas camadas | Lista de listas, quando você sabe que são só duas camadas |
copy.deepcopy(a) | Cópia profunda, todas as camadas | Objetos aninhados que você vai modificar |
A cópia profunda faz mais trabalho (visita cada objeto lá dentro) e ocupa mais memória. Por isso o costume é usar a rasa quando ela basta, e a profunda quando há objetos mutáveis dentro que vão mudar.
Quando dá errado
*: todas as linhas mudam juntas
A Ana quer uma tabela de vendas com 2 dias e 3 turnos, começando com zeros. O jeito "esperto" parece ser multiplicar listas:
vendas = [[0] * 3] * 2 vendas[0][1] = 5 print(vendas) print(vendas[0] is vendas[1])
$ python3 grade.py [[0, 5, 0], [0, 5, 0]] True
Ela mudou só o dia 0, e o 5 apareceu nos dois dias. O [[0] * 3] cria uma linha, e o * 2 repete a etiqueta dessa linha, sem copiar nada: vendas[0] is vendas[1] dá True. É a cópia rasa de novo, só que escondida. (O [0] * 3 de dentro não tem problema: repete o número 0, que é imutável.)
Como consertar: crie uma linha nova para cada dia, com uma list comprehension:
vendas = [[0] * 3 for _ in range(2)] vendas[0][1] = 5 print(vendas) print(vendas[0] is vendas[1])
$ python3 grade_certa.py [[0, 5, 0], [0, 0, 0]] False
O _ é o nome de costume para uma variável que não vai ser usada: só queremos repetir duas vezes.
import copy
turma = [["Bia", 8.5, 7.0], ["Caio", 6.0, 5.5]] profunda = copy.deepcopy(turma)
$ python3 sem_import.py
Traceback (most recent call last):
File "/home/ana/sem_import.py", line 2, in <module>
profunda = copy.deepcopy(turma)
^^^^
NameError: name 'copy' is not defined. Did you forget to import 'copy'?
"O nome copy não está definido. Você esqueceu de importar copy?" O Python 3.14 reconhece o nome de um módulo da biblioteca padrão e já sugere o conserto. Como consertar: import copy no começo do arquivo. (Repare que o método turma.copy() não precisa de import; o módulo copy, sim.)
.copy sem os parênteses
compras = ["café", "leite"]
copia = compras.copy
copia.append("pão")
$ python3 sem_parenteses.py
Traceback (most recent call last):
File "/home/ana/sem_parenteses.py", line 3, in <module>
copia.append("pão")
^^^^^^^^^^^^
AttributeError: 'builtin_function_or_method' object has no attribute 'append'
"Um objeto do tipo função (builtin_function_or_method) não tem append." Sem os parênteses, compras.copy não chama o método: guarda o próprio método na variável. O erro só aparece na linha seguinte, quando você tenta usar a "cópia" como lista. Como consertar: copia = compras.copy().
Você viu nas seções 3 e 6: com listas (ou dicionários) dentro, mudar um item de dentro da cópia rasa muda o original, sem erro nenhum. O sinal para desconfiar: copia[0] is original[0] dá True. Como consertar: copy.deepcopy.
Referência rápida
| Escrita | O que faz |
|---|---|
import copy | Traz o módulo de cópias |
copy.copy(x) | Cópia rasa: objeto novo, itens compartilhados |
copy.deepcopy(x) | Cópia profunda: objeto novo e cópias de tudo o que há dentro |
lista.copy(), list(lista), lista[:] | Cópia rasa de uma lista |
dicionario.copy(), dict(dicionario) | Cópia rasa de um dicionário |
[linha.copy() for linha in tabela] | Copia duas camadas |
[[0] * 3 for _ in range(2)] | Tabela com linhas independentes |
copia is original | False em qualquer cópia |
copia[0] is original[0] | True na rasa, False na profunda (para itens mutáveis) |
| Sintoma | Causa provável |
|---|---|
| Mudei a "cópia" e tudo mudou, inclusive pôr e tirar itens | Não foi cópia: b = a |
| Pôr e tirar itens funciona, mas mudar um item de dentro muda o original | Cópia rasa com objetos mutáveis dentro: use deepcopy |
| Mudei uma linha da tabela e todas mudaram | Tabela criada com [[…]] * n |
NameError: name 'copy' is not defined. Did you forget to import 'copy'? | Faltou import copy |
'builtin_function_or_method' object has no attribute … | Faltaram os parênteses: .copy() |
Exercícios
1. A cópia profunda
Complete o nome da função que copia todas as camadas:
independente = copy.(turma)
2. Rasa ou profunda?
c = copy.copy(turma), com turma = [["Bia", 8.5]]. O que mostra print(c[0] is turma[0])?
3. Qual destas escritas faz uma cópia profunda?
4. rasa = turma.copy() e depois rasa.append(["Duda", 9.0]). A turma original…
5. compras = ["café", "leite"]. Por que compras.copy() basta aqui, sem deepcopy?
6. Por que [[0] * 3] * 2 é uma armadilha?
Missão final: a tabela de vendas
Missão: a Ana registrou 5 vendas no dia 0, turno 1, mas o 5 apareceu nos dois dias. Mude a criação da tabela para que cada dia tenha a sua própria lista.
vendas = [[0] * 3] * 2 vendas[0][1] = 5 print(vendas)
vendas = [[0] * 3 for _ in range(2)] vendas[0][1] = 5 print(vendas)
Resumo
b = anão copia: é mais uma etiqueta no mesmo objeto.- A cópia rasa (
copy.copy,.copy(),list(),[:]) cria um objeto novo só na primeira camada; os itens de dentro são compartilhados. Pôr e tirar itens é seguro; mudar um item mutável de dentro muda o original. - A cópia profunda (
copy.deepcopy) copia todas as camadas: o resultado é totalmente independente. - Para saber o que é compartilhado, use
is:copia[0] is original[0]. - Com itens imutáveis (textos, números), a cópia rasa basta. Com listas, dicionários ou objetos dentro que vão mudar, use a profunda.
- Dicionários e objetos das suas classes seguem a mesma regra.
[[0] * 3] * 2repete a mesma linha; crie linhas independentes com[[0] * 3 for _ in range(2)].