copy no Python: cópia rasa e profunda

Publicado em: 28/07/2026
Tempo de leitura: 8 minutos
Ícone de documentos duplicados representando cópia rasa e profunda no Python

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)  # True

As 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])  # True

A 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)     # False

A 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)    # False

Essa 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 = gateway

Nesse 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 novo

A 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)  # True

Use 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"])  # 30

A 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]) # True

Essa 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 = a cria 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 o memo.
  • 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.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Monitor com busca binária e listas ordenadas no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    bisect no Python: listas sempre ordenadas

    Aprenda bisect no Python para manter listas ordenadas, encontrar intervalos e inserir valores com busca binária eficiente.

    Ler mais

    Tempo de leitura: 8 minutos
    27/07/2026
    Desenvolvedor implementando fila de prioridade com heapq no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    heapq no Python: filas de prioridade

    Aprenda heapq no Python para criar filas de prioridade, encontrar menores valores e processar tarefas com heaps eficientes.

    Ler mais

    Tempo de leitura: 7 minutos
    26/07/2026
    Como acelerar código Python usando lru cache
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    Como acelerar seu código Python com @lru_cache em 2 minutos

    Você já sentiu que seu programa está demorando uma eternidade para processar cálculos repetitivos? Sabia que existe uma forma mágica

    Ler mais

    Tempo de leitura: 10 minutos
    06/04/2026
    Leitura segura de senhas no terminal usando Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    Como ler senhas de forma segura no terminal com Python

    Entender como ler senhas de forma segura no terminal com Python é um passo fundamental para qualquer desenvolvedor que deseja

    Ler mais

    Tempo de leitura: 8 minutos
    02/04/2026
    Como evitar KeyError usando defaultdict em Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    Como evitar KeyError usando defaultdict no Python

    Você já tentou acessar uma chave em um dicionário e se deparou com aquele erro vermelho interrompendo seu script? O

    Ler mais

    Tempo de leitura: 9 minutos
    30/03/2026
    Monitoramento de pastas em tempo real com Python Watchdog
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    Como monitorar pastas em tempo real com Python e watchdog

    Monitorar pastas em tempo real com Python e watchdog é uma das formas mais eficientes de criar sistemas automatizados que

    Ler mais

    Tempo de leitura: 9 minutos
    26/03/2026