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

    Documento e caixa de entrada representando caixas de e-mail com mailbox no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    mailbox no Python: caixas de e-mail

    Aprenda mailbox no Python para ler, criar e migrar caixas Maildir, mbox e MH com locking, mensagens, flags e tratamento

    Ler mais

    Tempo de leitura: 6 minutos
    12/08/2026
    Editor de texto representando formatação com textwrap no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    textwrap no Python: formate textos

    Aprenda textwrap no Python para quebrar, preencher, encurtar, indentar e remover recuos de textos com controle de largura e espaços.

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026
    Pasta e lupa representando filtros de nomes com fnmatch no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    fnmatch no Python: filtre nomes de arquivos

    Aprenda fnmatch no Python para filtrar nomes de arquivos com curingas, controlar maiúsculas, excluir padrões e evitar confundir glob com

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026
    Monitor com dados binários representando arrays numéricos compactos no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    array no Python: números compactos

    Aprenda array no Python para armazenar números compactos, manipular bytes, arquivos binários e buffers com segurança.

    Ler mais

    Tempo de leitura: 6 minutos
    10/08/2026
    Círculo cromático representando conversões RGB, HSV e HLS com colorsys no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    colorsys no Python: RGB, HSV e HLS

    Aprenda colorsys no Python para converter cores entre RGB, HSV, HLS e YIQ, gerar paletas e evitar erros com escalas

    Ler mais

    Tempo de leitura: 6 minutos
    09/08/2026
    Ícone de configuração representando arquivos plist com plistlib no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    plistlib no Python: arquivos plist

    Aprenda plistlib no Python para ler e gravar arquivos plist XML e binários, validar dados e integrar configurações Apple com

    Ler mais

    Tempo de leitura: 7 minutos
    08/08/2026