Se você já tentou copiar uma lista ou dicionário em Python e acabou modificando os dois sem querer, este guia é para você. O comportamento de cópia em Python é uma das fontes mais comuns de bugs sutis, especialmente para quem está começando. Entender a diferença entre cópia rasa (shallow copy) e cópia profunda (deep copy) é essencial para escrever código previsível e livre de efeitos colaterais.

Neste guia completo, você vai aprender desde o conceito de atribuição vs cópia até técnicas avançadas com o módulo copy da biblioteca padrão. Vamos explorar exemplos práticos com listas, dicionários, objetos aninhados e classes personalizadas, além de discutir performance e boas práticas.

Atribuição Não é Cópia

Antes de mergulharmos nos tipos de cópia, é fundamental entender que atribuir uma variável a outra não cria uma cópia. Em Python, variáveis são rótulos (referências) para objetos na memória. Quando você faz b = a, você está apenas criando um novo rótulo para o mesmo objeto. A documentação oficial do Python sobre atribuição explica que o operador = vincula o nome ao objeto, sem duplicá-lo.

# Atribuição comum — não é cópia
lista_original = [1, 2, [3, 4]]
lista_referencia = lista_original

lista_referencia.append(5) print(lista_original) # [1, 2, [3, 4], 5] — a original foi alterada!

Esse comportamento ocorre porque lista_referencia e lista_original apontam para o mesmo objeto na memória. Qualquer modificação feita por uma referência afeta a outra. Esse é o conceito fundamental que motiva a necessidade de métodos de cópia.

O Que é Shallow Copy (Cópia Rasa)?

A shallow copy (cópia rasa) cria um novo objeto, mas não duplica os objetos internos. Em vez disso, ela copia as referências para os elementos contidos no objeto original. Isso significa que o objeto de nível superior é novo, mas os objetos aninhados (como listas dentro de listas) continuam sendo compartilhados entre a cópia e o original.

O Python oferece várias formas de criar shallow copies. A mais comum é usando o módulo copy:

import copy

lista_original = [1, 2, [3, 4]] lista_copia = copy.copy(lista_original) # shallow copy

lista_copia.append(5) # só afeta a cópia lista_copia[2].append(5) # afeta AMBAS! Objeto interno é compartilhado

print(lista_original) # [1, 2, [3, 4, 5]] print(lista_copia) # [1, 2, [3, 4, 5], 5]

Perceba que append(5) no nível superior só modificou a cópia, mas append(5) na lista aninhada modificou ambos. Isso porque a lista interna [3, 4] não foi duplicada — apenas a referência para ela foi copiada. A documentação oficial do módulo copy descreve esse comportamento em detalhes.

Métodos de Shallow Copy

Além de copy.copy(), existem outras maneiras de criar shallow copies em Python:

  • Método .copy() de listas e dicionários: lista.copy() e dict.copy() criam shallow copies.
  • Fatiamento ([:]): nova_lista = lista_original[:] cria uma shallow copy da lista.
  • Construtor list() e dict(): list(original) e dict(original) também criam shallow copies.
  • Módulo copy: copy.copy(objeto) funciona com qualquer tipo de objeto.

Para quem trabalha com listas em Python, dominar essas técnicas é fundamental para evitar comportamentos inesperados. A Real Python possui um tutorial aprofundado sobre cópia de objetos em Python que vale a pena consultar.

O Que é Deep Copy (Cópia Profunda)?

A deep copy (cópia profunda) cria um novo objeto e, recursivamente, duplica todos os objetos aninhados encontrados. O resultado é uma cópia completamente independente do original, sem qualquer referência compartilhada. Qualquer modificação na cópia profunda não afeta o original, e vice-versa.

import copy

lista_original = [1, 2, [3, 4]] lista_deep = copy.deepcopy(lista_original)

lista_deep.append(5) lista_deep[2].append(5)

print(lista_original) # [1, 2, [3, 4]] — intacta! print(lista_deep) # [1, 2, [3, 4, 5], 5]

A função copy.deepcopy() percorre toda a árvore de objetos e cria duplicatas de cada nó encontrado. Isso garante isolamento total entre a cópia e o original, mas tem um custo computacional mais alto, especialmente para estruturas grandes ou que contêm muitos níveis de aninhamento. A discussão no Stack Overflow sobre o assunto é uma das mais votadas da plataforma, mostrando como esse tema é relevante.

Como o deepcopy Funciona Internamente

O copy.deepcopy() usa um dicionário interno de "memória" (memo) para rastrear objetos já copiados. Isso é essencial para evitar loops infinitos em estruturas com referências circulares e para garantir que objetos compartilhados não sejam duplicados múltiplas vezes. Se você está estudando dicionários em Python, entender deep copy é fundamental, já que dicionários com valores aninhados são um dos casos de uso mais comuns.

O mecanismo pode ser resumido em três etapas:

  1. Verificação do memo: Se o objeto já foi copiado, retorna a cópia existente.
  2. Criação do novo objeto: Usa __deepcopy__ ou __copy__ se definidos, ou cria um novo objeto vazio do mesmo tipo.
  3. Cópia recursiva: Para cada atributo ou elemento, chama deepcopy recursivamente e armazena no novo objeto.

A PEP 8 — Guia de Estilo Python recomenda que métodos de cópia sejam implementados de forma explícita nas classes que precisam de comportamento personalizado.

Shallow Copy vs Deep Copy: Comparação Direta

Vamos consolidar as diferenças em uma tabela comparativa para facilitar a consulta rápida:

CaracterísticaShallow CopyDeep Copy
Cria novo objeto de topo?SimSim
Copia objetos aninhados?Não (apenas referências)Sim (recursivamente)
PerformanceRápidaMais lenta
Uso de memóriaBaixoAlto
Isolamento total?NãoSim
Funciona com objetos personalizados?Sim (padrão)Sim (padrão)
Suporta referências circulares?Não causa problemaSim (via memo)

Cópia em Diferentes Estruturas de Dados

Cópia de Listas

Listas são a estrutura mais comum onde a diferença entre shallow e deep copy aparece. Veja exemplos com diferentes níveis de aninhamento:

import copy

Lista simples (sem aninhamento)

simples = [1, 2, 3] s_shallow = copy.copy(simples) s_deep = copy.deepcopy(simples)

Ambos funcionam de forma equivalente aqui, pois não há objetos aninhados

Lista aninhada

aninhada = [[1, 2], [3, 4]] a_shallow = copy.copy(aninhada) a_deep = copy.deepcopy(aninhada)

aninhada[0].append(99) print(aninhada) # [[1, 2, 99], [3, 4]] print(a_shallow) # [[1, 2, 99], [3, 4]] — afetado! print(a_deep) # [[1, 2], [3, 4]] — isolado

Cópia de Dicionários

import copy

dados = {"nome": "Maria", "endereco": {"cidade": "SP", "cep": "01001"}}

copia_rasa = copy.copy(dados) copia_profunda = copy.deepcopy(dados)

copia_rasa["endereco"]["cidade"] = "RJ" print(dados["endereco"]["cidade"]) # RJ — shallow copy compartilha o dict interno

copia_profunda["endereco"]["cidade"] = "BH" print(dados["endereco"]["cidade"]) # RJ — deep copy manteve o original intacto

Dicionários com valores aninhados (como o campo "endereco" acima) são a situação mais comum onde shallow copy causa surpresas. Sempre que seu dicionário contiver outros dicionários, listas ou objetos como valores, você deve considerar usar deep copy se precisar de isolamento completo.

Cópia de Objetos Personalizados

Para classes que você mesmo cria, o comportamento de cópia pode ser personalizado implementando os métodos __copy__ e __deepcopy__:

import copy

class Endereco: def init(self, cidade, cep): self.cidade = cidade self.cep = cep

class Pessoa: def init(self, nome, endereco): self.nome = nome self.endereco = endereco

def __copy__(self):
    # Shallow copy personalizada
    return Pessoa(self.nome, self.endereco)

def __deepcopy__(self, memo):
    # Deep copy personalizada
    return Pessoa(
        copy.deepcopy(self.nome, memo),
        copy.deepcopy(self.endereco, memo)
    )

end = Endereco("SP", "01001") p1 = Pessoa("João", end) p2 = copy.deepcopy(p1)

p2.endereco.cidade = "RJ" print(p1.endereco.cidade) # SP — isolado graças ao deepcopy

A documentação do método __deepcopy__ explica que o parâmetro memo é um dicionário que rastreia objetos já copiados, evitando duplicação e loops infinitos.

Quando Usar Cada Tipo de Cópia?

Escolher entre shallow copy e deep copy depende do seu caso de uso. Aqui estão diretrizes práticas:

Prefira Shallow Copy Quando:

  • Os objetos contidos são imutáveis (ints, strings, tuplas sem objetos mutáveis).
  • Você quer eficiência e a estrutura tem apenas um nível de profundidade.
  • Você intencionalmente quer compartilhar objetos internos (ex: um cache compartilhado).
  • Está trabalhando com grandes volumes de dados onde deep copy seria muito caro.

Prefira Deep Copy Quando:

  • Há objetos mutáveis aninhados em múltiplos níveis.
  • Você precisa de isolamento completo entre original e cópia.
  • Está implementando padrões como snapshot de estado ou rollback.
  • Você não tem controle sobre quem vai modificar os objetos internos.

Performance e Boas Práticas

A deep copy é significativamente mais custosa que a shallow copy, tanto em tempo de execução quanto em uso de memória. Para objetos grandes, a diferença pode ser de ordens de magnitude. Se você está processando gigabytes de dados, uma deep copy pode facilmente consumir toda a memória disponível.

Algumas boas práticas para trabalhar com cópia em Python:

  1. Documente o comportamento: Se sua classe implementa __copy__ ou __deepcopy__, documente o que é copiado rasa ou profundamente.
  2. Prefira imutabilidade: Use tuplas, frozensets e tipos imutáveis sempre que possível. Eles nunca precisam ser copiados profundamente.
  3. Considere alternativas: Em vez de copiar, às vezes é melhor projetar seu código para não precisar de cópia (ex: usando factory functions).
  4. Cuidado com deepcopy em objetos complexos: Objetos com conexões de rede, arquivos abertos ou locks não devem ser copiados com deepcopy.

A documentação oficial do deepcopy alerta que objetos como módulos, classes, funções, tipos de máquina, arquivos e generators não podem ser copiados. O módulo pickle é uma alternativa para serialização e cópia de objetos em alguns cenários.

Casos Avançados

Referências Circulares

Estruturas com referências circulares (um objeto que contém uma referência para si mesmo) são tratadas corretamente pelo deepcopy graças ao parâmetro memo:

import copy

class Node: def init(self, valor): self.valor = valor self.proximo = None

a = Node(1) b = Node(2) a.proximo = b b.proximo = a # referência circular!

copia = copy.deepcopy(a) # funciona sem problemas print(copia.proximo.proximo.valor) # 1

Cópia com Filtros

Você pode controlar quais atributos são copiados implementando __deepcopy__ de forma seletiva:

import copy

class Config: def init(self): self.dados = {"chave": "valor"} self.cache = {"temp": "dados"} # não queremos copiar isso

def __deepcopy__(self, memo):
    novo = Config()
    novo.dados = copy.deepcopy(self.dados, memo)
    # cache não é copiado — será recriado vazio
    return novo

Erros Comuns e Como Evitá-los

Aqui estão os erros mais frequentes ao trabalhar com cópia em Python e como evitá-los:

Erro #1: Assumir que = cria uma cópia

Como vimos no início, b = a cria uma nova referência, não uma cópia. Sempre use copy() ou deepcopy() quando precisar de independência.

Erro #2: Usar copy.copy() em estruturas profundamente aninhadas

Se sua estrutura tem objetos mutáveis em múltiplos níveis, shallow copy não é suficiente. Use deepcopy() ou repense o design.

Erro #3: Aplicar deepcopy indiscriminadamente

Deep copy é cara. Use-a apenas quando necessário. Para objetos simples ou imutáveis, shallow copy é suficiente e muito mais eficiente. O artigo do GeeksForGeeks sobre copy em Python mostra exemplos práticos dessa diferença de performance.

Erro #4: Ignorar objetos não copiáveis

Nem todos os objetos podem ser copiados com deepcopy. Conexões de banco, sockets, threads e objetos de sistema operacional falham. Sempre verifique a documentação da biblioteca que você está usando. A documentação oficial de estruturas de dados do Python é um excelente ponto de partida para entender a fundo esses conceitos.

Conclusão

Dominar a diferença entre copy e deep copy em Python é uma habilidade essencial para qualquer desenvolvedor que trabalha com estruturas de dados mutáveis. A escolha correta entre cópia rasa e profunda pode evitar bugs difíceis de encontrar e melhorar significativamente a previsibilidade do seu código.

A regra de ouro é simples: use shallow copy para objetos planos ou quando você sabe que os objetos internos são imutáveis; use deep copy quando houver objetos mutáveis aninhados e você precisar de isolamento completo. E, acima de tudo, lembre-se de que atribuição (=) nunca é cópia.

Continue explorando o Universo Python com nossos guias gratuitos sobre listas em Python e dicionários em Python para aprofundar seu conhecimento em manipulação de dados na linguagem.