Atribuir uma lista, um dicionário ou uma instância a outra variável não cria automaticamente um novo objeto. Em Python, a atribuição normalmente cria apenas outro nome apontando para o mesmo valor. Isso explica por que uma alteração feita por uma variável pode aparecer inesperadamente na outra. O módulo copy no Python existe para controlar esse comportamento com cópias rasas, cópias profundas e substituições imutáveis de campos.
Neste guia, você aprenderá a diferença entre identidade e igualdade, quando usar copy.copy(), quando recorrer a copy.deepcopy(), como funciona o dicionário memo, como personalizar cópias com __copy__() e __deepcopy__() e como usar copy.replace(). O conteúdo complementa os artigos sobre listas em Python, fatiamento de sequências, estruturas do módulo collections, descriptors em Python e vazamentos de memória.
Atribuição não é cópia
Observe o exemplo a seguir:
original = [1, 2, 3]
apelido = original
apelido.append(4)
print(original) # [1, 2, 3, 4]
print(apelido is original) # TrueAs duas variáveis apontam para a mesma lista. A operação append() altera o objeto compartilhado. O operador is confirma identidade: ambos os nomes apontam para exatamente o mesmo objeto.
Essa característica é eficiente e normalmente desejável. O problema surge quando o programa pretende preservar uma versão independente. Nesse caso, é necessário construir outro objeto.
Cópia rasa com copy.copy()
copy.copy() cria um novo objeto composto, mas reutiliza as referências para os objetos internos. Por isso, ela é chamada de cópia rasa.
import copy
original = [[1, 2], [3, 4]]
raso = copy.copy(original)
print(raso is original) # False
print(raso[0] is original[0]) # TrueA lista externa é nova, mas as listas internas continuam compartilhadas. Se você adicionar uma nova linha apenas à cópia, o original não muda. Porém, se modificar uma linha existente, ambos verão a alteração.
raso.append([5, 6])
raso[0].append(99)
print(original) # [[1, 2, 99], [3, 4]]
print(raso) # [[1, 2, 99], [3, 4], [5, 6]]Métodos nativos de cópia
Listas, dicionários e conjuntos possuem métodos próprios:
lista_nova = lista_antiga.copy()
dicionario_novo = dicionario_antigo.copy()
conjunto_novo = conjunto_antigo.copy()Para listas e alguns outros tipos de sequência, um slice completo também cria uma cópia rasa:
nova = antiga[:]Essas formas são claras quando você trabalha com um tipo específico. copy.copy() é útil em funções genéricas, em instâncias personalizadas e quando é importante preservar o tipo derivado. A documentação oficial do módulo copy observa que alguns métodos nativos e slices podem produzir uma instância do tipo base ao copiar subclasses, enquanto copy.copy() normalmente preserva o tipo concreto.
Cópia profunda com copy.deepcopy()
copy.deepcopy() percorre recursivamente o objeto composto e cria cópias dos componentes internos. Isso evita o compartilhamento acidental de estruturas mutáveis.
import copy
original = {
"usuario": "Ana",
"permissoes": ["ler", "editar"],
"preferencias": {"tema": "escuro"},
}
profundo = copy.deepcopy(original)
profundo["permissoes"].append("publicar")
profundo["preferencias"]["tema"] = "claro"
print(original["permissoes"])
print(original["preferencias"])As listas e os dicionários aninhados foram copiados. Alterações na cópia não atingem o original.
Deepcopy não copia tudo cegamente
Uma cópia profunda não significa que cada valor receberá uma nova identidade. Objetos imutáveis, funções e classes podem ser reutilizados porque não precisam ser duplicados. Tipos como módulos, arquivos abertos, sockets, frames e janelas não são copiados de forma geral.
Além disso, copiar todo o grafo de objetos pode ser caro ou incorreto. Alguns componentes devem permanecer compartilhados, como conexões, caches globais, registros, locks e serviços externos. Por isso, use deepcopy() somente quando a independência recursiva fizer parte do contrato do programa.
O problema das referências circulares
Estruturas podem apontar para si mesmas:
lista = []
lista.append(lista)Uma implementação ingênua de cópia recursiva entraria em loop infinito. deepcopy() evita isso mantendo um dicionário interno chamado memo. Ele associa a identidade dos objetos já processados às respectivas cópias.
import copy
lista = []
lista.append(lista)
copia = copy.deepcopy(lista)
print(copia is copia[0]) # True
print(copia is lista) # FalseA relação circular é preservada sem reutilizar a lista original.
Por que o memo é importante
O memo também preserva o compartilhamento interno. Se duas partes do objeto original apontam para o mesmo componente, a cópia profunda normalmente fará com que ambas apontem para a mesma cópia desse componente, em vez de criar duplicatas inconsistentes.
import copy
config = {"limite": 10}
original = [config, config]
copia = copy.deepcopy(original)
print(copia[0] is copia[1]) # True
print(copia[0] is config) # FalseEssa propriedade é essencial em grafos de objetos. Sem ela, relações de identidade importantes seriam destruídas.
Cópia profunda pode copiar demais
Imagine uma classe que contém dados de negócio e também uma referência para um serviço compartilhado. Uma cópia profunda automática pode tentar duplicar componentes que deveriam continuar únicos.
class Pedido:
def __init__(self, itens, gateway):
self.itens = itens
self.gateway = gatewayNesse caso, talvez os itens devam ser copiados, mas o gateway de pagamentos deve continuar compartilhado. A solução é personalizar o comportamento.
Personalizando com __copy__()
Uma classe pode implementar __copy__() para controlar a cópia rasa:
import copy
class Documento:
def __init__(self, titulo, metadados):
self.titulo = titulo
self.metadados = metadados
def __copy__(self):
return type(self)(self.titulo, self.metadados)
doc = Documento("Relatório", {"versao": 1})
novo = copy.copy(doc)
print(novo is doc)
print(novo.metadados is doc.metadados)O novo documento é independente, mas os metadados permanecem compartilhados, que é exatamente a semântica rasa.
Personalizando com __deepcopy__()
__deepcopy__(self, memo) recebe o dicionário de memoização. Trate esse argumento como uma estrutura opaca e repasse-o a chamadas recursivas de copy.deepcopy().
import copy
class Pedido:
def __init__(self, itens, gateway):
self.itens = itens
self.gateway = gateway
def __deepcopy__(self, memo):
novo = type(self)(
copy.deepcopy(self.itens, memo),
self.gateway,
)
memo[id(self)] = novo
return novoA lista de itens é copiada profundamente, mas o gateway permanece compartilhado. Registrar o novo objeto no memo é especialmente importante quando existem ciclos ou referências repetidas.
Objetos imutáveis e copy.replace()
Desde o Python 3.13, copy.replace() cria um novo objeto do mesmo tipo substituindo apenas campos especificados. É uma alternativa expressiva para estruturas imutáveis.
from copy import replace
from dataclasses import dataclass
@dataclass(frozen=True)
class Produto:
nome: str
preco: float
estoque: int
original = Produto("Teclado", 120.0, 15)
promocao = replace(original, preco=99.9)
print(original)
print(promocao)O objeto original permanece intacto. A função suporta dataclasses, named tuples e classes que implementam __replace__().
copy.replace() não é deepcopy()
replace() substitui campos específicos e reutiliza os demais valores. Se um campo não substituído contém uma lista mutável, essa lista continuará compartilhada.
from copy import replace
from dataclasses import dataclass
@dataclass(frozen=True)
class Plano:
nome: str
etapas: list[str]
base = Plano("Lançamento", ["teste", "produção"])
novo = replace(base, nome="Lançamento v2")
print(novo.etapas is base.etapas) # TrueUse replace() para atualização funcional de registros, não como sinônimo de independência recursiva.
Implementando __replace__()
Classes personalizadas podem aderir ao protocolo:
class Coordenada:
def __init__(self, x, y):
self.x = x
self.y = y
def __replace__(self, **changes):
permitidos = {"x", "y"}
desconhecidos = set(changes) - permitidos
if desconhecidos:
raise TypeError(f"Campos inválidos: {desconhecidos}")
return type(self)(
changes.get("x", self.x),
changes.get("y", self.y),
)Com isso, copy.replace(coordenada, x=10) cria uma nova instância com uma alteração controlada.
Cópia rasa em dicionários aninhados
Um erro frequente acontece com configurações:
padrao = {
"api": {"timeout": 10},
"logs": {"nivel": "INFO"},
}
producao = padrao.copy()
producao["api"]["timeout"] = 30
print(padrao["api"]["timeout"]) # 30A cópia externa não duplica os dicionários internos. Para configurações independentes, use uma construção explícita, merge controlado ou deepcopy() quando todos os componentes forem seguros para duplicação.
Cópia de listas de objetos
Copiar uma lista não copia as instâncias armazenadas:
usuarios_copia = usuarios.copy()
print(usuarios_copia is usuarios) # False
print(usuarios_copia[0] is usuarios[0]) # TrueEssa semântica pode ser desejável quando a lista representa apenas uma visão ou ordenação diferente dos mesmos objetos. Se cada usuário precisa ser independente, avalie uma cópia profunda ou um método explícito como clone().
Quando preferir reconstrução explícita
Para modelos de domínio, muitas vezes é melhor criar o novo valor de forma explícita. Isso deixa visível quais campos são compartilhados, recalculados ou substituídos.
novo_pedido = Pedido(
itens=[item.copiar() for item in pedido.itens],
gateway=pedido.gateway,
)A reconstrução explícita é mais trabalhosa, mas pode produzir um contrato mais claro que uma cópia profunda genérica.
Desempenho e memória
Cópias rasas normalmente custam tempo proporcional ao número de referências do contêiner externo. Cópias profundas percorrem o grafo alcançável e podem consumir muito mais CPU e memória.
Evite duplicar estruturas grandes em loops sem medir. Considere dados imutáveis, compartilhamento estrutural, atualizações por campo, generators ou armazenamento persistente. O tutorial oficial de estruturas de dados ajuda a entender as operações de listas, dicionários, conjuntos e sequências que influenciam essas decisões.
Testando a independência correta
Não teste apenas igualdade. Valide também identidade e efeitos de mutação:
import copy
original = {"itens": [[1], [2]]}
copia = copy.deepcopy(original)
assert copia == original
assert copia is not original
assert copia["itens"] is not original["itens"]
assert copia["itens"][0] is not original["itens"][0]
copia["itens"][0].append(9)
assert original["itens"][0] == [1]Para cópias personalizadas, adicione testes para ciclos, referências repetidas, recursos compartilhados e subclasses.
Erros comuns
- Acreditar que
b = acria um novo objeto. - Usar
list.copy()esperando duplicar elementos internos. - Aplicar
deepcopy()automaticamente a conexões, sockets, locks ou serviços. - Ignorar relações de identidade importantes dentro do grafo.
- Implementar
__deepcopy__()sem repassar omemo. - Usar
copy.replace()como se fosse uma cópia profunda. - Duplicar estruturas grandes sem medir custo.
- Copiar objetos quando uma reconstrução explícita seria mais clara.
Boas práticas
- Defina primeiro qual nível de independência o código precisa.
- Use cópia rasa quando apenas o contêiner externo deve mudar.
- Use cópia profunda quando todo o estado mutável alcançável precisa ser independente.
- Personalize classes que possuem recursos compartilhados.
- Prefira
copy.replace()para atualizações funcionais de registros compatíveis. - Documente quais componentes permanecem compartilhados.
- Teste igualdade, identidade e mutações posteriores.
- Meça tempo e memória em estruturas grandes.
Conclusão
O módulo copy no Python resolve um problema central da linguagem: distinguir entre criar outro nome, copiar apenas o contêiner externo e duplicar recursivamente um grafo de objetos. copy.copy() cria uma cópia rasa; copy.deepcopy() usa memoização para tratar ciclos e preservar compartilhamentos internos; copy.replace() cria novas versões de registros substituindo campos específicos.
A melhor escolha depende do modelo de propriedade. Cópias rasas são rápidas e preservam componentes compartilhados. Cópias profundas fornecem independência, mas podem copiar demais. Protocolos personalizados permitem combinar as duas estratégias. Quando essa decisão é explícita e testada, o código evita alterações inesperadas, consumo excessivo de memória e objetos duplicados de forma incorreta.







