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.

Você vai aprender
  • 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 is o 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] * 2 e 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 dentroCópia rasa basta?
Textos, números, True/False, NoneSim
Tuplas só de imutáveisSim
Listas, dicionários, conjuntos, objetos que você vai modificarNão: use copy.deepcopy (ou copie cada camada)
Listas de dentro que você só vai ler, nunca mudarSim (mas cuidado se o programa crescer)

Formas diferentes de fazer a mesma coisa

EscritaO que éQuando usar
b = aNenhuma cópia: mais uma etiquetaQuando 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 rasaTambém comuns; list(x) serve para copiar de qualquer coisa que se percorre
copy.copy(a)Cópia rasa de qualquer objetoObjetos que não têm .copy() (os das suas classes)
[x.copy() for x in a]Cópia de duas camadasLista de listas, quando você sabe que são só duas camadas
copy.deepcopy(a)Cópia profunda, todas as camadasObjetos 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

A tabela feita com *: 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.

Esqueceu o 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().

A cópia rasa que muda o original

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

EscritaO que faz
import copyTraz 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 originalFalse em qualquer cópia
copia[0] is original[0]True na rasa, False na profunda (para itens mutáveis)
SintomaCausa provável
Mudei a "cópia" e tudo mudou, inclusive pôr e tirar itensNão foi cópia: b = a
Pôr e tirar itens funciona, mas mudar um item de dentro muda o originalCópia rasa com objetos mutáveis dentro: use deepcopy
Mudei uma linha da tabela e todas mudaramTabela 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:

import copy
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 = a nã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] * 2 repete a mesma linha; crie linhas independentes com [[0] * 3 for _ in range(2)].