copy.replace: atualize objetos imutáveis no Python

Publicado em: 01/10/2026
Tempo de leitura: 6 minutos
Programador trabalhando com objetos imutáveis e copy.replace no Python

copy.replace no Python permite criar uma nova versão de um objeto substituindo apenas alguns campos, sem alterar a instância original. O recurso é útil para trabalhar com dados imutáveis, configurações, resultados de processamento, objetos de domínio e estruturas que precisam ser atualizadas de forma previsível.

Em vez de modificar atributos diretamente, você descreve quais valores devem mudar e recebe outro objeto. Essa abordagem combina bem com programação funcional, testes, concorrência e código em que efeitos colaterais precisam ser controlados.

O que é copy.replace

copy.replace(obj, **changes) cria um novo objeto do mesmo tipo de obj, substituindo os campos informados em changes. Ele não é uma cópia profunda: campos não alterados continuam seguindo as regras definidas pelo tipo do objeto.

O suporte depende do protocolo __replace__. Tipos compatíveis podem implementar esse método para dizer ao Python como construir a nova instância. Entre os casos mais importantes estão named tuples, dataclasses e classes personalizadas que implementam o protocolo.

from copy import replace
from dataclasses import dataclass

@dataclass(frozen=True)
class Usuario:
    nome: str
    email: str
    ativo: bool = True

original = Usuario("Ana", "ana@example.com")
atualizado = replace(original, email="ana@empresa.com")

print(original)
print(atualizado)

O objeto original permanece intacto. A nova instância recebe o email atualizado e preserva os demais valores.

Por que usar em vez de alterar atributos

Alterar atributos diretamente é simples, mas pode gerar efeitos colaterais quando o mesmo objeto é compartilhado por diferentes partes do programa. Um componente pode modificar um valor que outro componente ainda esperava encontrar.

Com substituição imutável, cada etapa recebe uma nova versão. Isso facilita rastrear mudanças, comparar estados, reverter alterações e escrever testes mais previsíveis.

config_nova = replace(config_antiga, timeout=30)

Essa linha comunica claramente que uma nova configuração será criada. Não há dúvida sobre mutação da instância anterior.

copy.replace com dataclasses

Dataclasses são um dos usos mais naturais. Elas permitem definir estruturas de dados com pouco código e podem ser congeladas com frozen=True.

from dataclasses import dataclass
from copy import replace

@dataclass(frozen=True)
class Produto:
    nome: str
    preco: float
    estoque: int

produto = Produto("Teclado", 199.90, 15)
promocao = replace(produto, preco=169.90)

O resultado é uma nova instância de Produto. O nome e o estoque são preservados, enquanto o preço muda.

Essa técnica é útil em pipelines de cálculo. Uma etapa pode adicionar desconto, outra pode calcular imposto e outra pode ajustar disponibilidade, sempre gerando novos estados.

Uso com namedtuple

Named tuples também representam registros leves e imutáveis. O padrão de substituição evita reconstruir manualmente todos os campos.

from collections import namedtuple
from copy import replace

Ponto = namedtuple("Ponto", "x y")
p1 = Ponto(10, 20)
p2 = replace(p1, y=25)

Sem esse recurso, você teria de lembrar a ordem dos argumentos ou usar métodos específicos do tipo. Uma interface comum reduz diferenças entre estruturas compatíveis.

Classes personalizadas com __replace__

Uma classe pode participar do protocolo implementando __replace__. O método deve validar os nomes recebidos e retornar uma nova instância.

class Conta:
    def __init__(self, titular, saldo, limite):
        self.titular = titular
        self.saldo = saldo
        self.limite = limite

    def __replace__(self, **changes):
        permitidos = {"titular", "saldo", "limite"}
        invalidos = set(changes) - permitidos
        if invalidos:
            raise TypeError(f"Campos inválidos: {invalidos}")

        dados = {
            "titular": self.titular,
            "saldo": self.saldo,
            "limite": self.limite,
        }
        dados.update(changes)
        return type(self)(**dados)

Uma implementação robusta deve rejeitar campos desconhecidos, manter invariantes e preservar subclasses quando isso fizer sentido.

Validação e invariantes

A substituição não deve permitir estados inválidos. Se uma conta não pode ter limite negativo, a validação precisa ocorrer no construtor ou em __replace__.

class Configuracao:
    def __init__(self, tentativas, timeout):
        if tentativas < 0:
            raise ValueError("tentativas não pode ser negativo")
        if timeout <= 0:
            raise ValueError("timeout deve ser positivo")
        self.tentativas = tentativas
        self.timeout = timeout

Ao centralizar validações no construtor, toda nova instância criada por substituição passa pelas mesmas regras.

Diferença para copy.copy

copy.copy faz uma cópia rasa do objeto. Ele duplica a camada externa, mas não oferece uma forma declarativa de alterar campos durante a operação.

from copy import copy, replace

copia = copy(original)
novo = replace(original, ativo=False)

Use copy.copy quando deseja apenas uma duplicação rasa. Use copy.replace quando deseja criar uma nova versão com mudanças explícitas.

Diferença para copy.deepcopy

copy.deepcopy tenta copiar recursivamente objetos internos. Isso pode ser caro e nem sempre é desejável. Objetos como conexões, locks e recursos externos não deveriam ser duplicados automaticamente.

copy.replace é mais preciso: apenas os campos indicados mudam. Os demais valores são reutilizados conforme a implementação do tipo.

Atenção a campos mutáveis

Substituição imutável da estrutura externa não significa que todos os campos internos sejam imutáveis.

from dataclasses import dataclass
from copy import replace

@dataclass(frozen=True)
class Pedido:
    itens: list[str]
    status: str

p1 = Pedido(["livro"], "novo")
p2 = replace(p1, status="pago")
p1.itens.append("caneta")

Nesse caso, as duas instâncias compartilham a mesma lista. Para evitar isso, use tipos imutáveis, como tuplas, ou crie uma nova coleção durante a substituição.

p2 = replace(p1, itens=(*p1.itens, "caneta"))

Configurações em camadas

Um uso prático é construir configurações em etapas. Você pode ter valores padrão, depois aplicar ajustes de ambiente e, por fim, opções fornecidas pelo usuário.

base = Config(timeout=10, debug=False, retries=2)
producao = replace(base, timeout=30)
local = replace(base, debug=True)

Cada configuração é independente no nível da estrutura e pode ser usada por diferentes componentes sem mutação acidental.

Eventos e estado de aplicações

Em aplicações orientadas a eventos, cada ação pode transformar um estado em outro.

def aplicar_pagamento(pedido, valor):
    novo_total = pedido.total_pago + valor
    status = "pago" if novo_total >= pedido.total else pedido.status
    return replace(pedido, total_pago=novo_total, status=status)

O histórico de estados pode ser armazenado para auditoria, depuração ou implementação de undo.

Testes mais simples

Em testes, é comum partir de um objeto padrão e alterar apenas o campo relevante para cada cenário.

usuario_base = Usuario("Ana", "ana@example.com", True)
usuario_inativo = replace(usuario_base, ativo=False)

Isso reduz duplicação e deixa claro qual propriedade diferencia cada caso. Também evita que um teste contamine outro por mutação compartilhada.

Compatibilidade entre versões

copy.replace é um recurso recente. Confira a versão mínima do Python do projeto antes de adotá-lo. Bibliotecas que precisam atender versões antigas podem oferecer uma função adaptadora.

try:
    from copy import replace
except ImportError:
    from dataclasses import replace

Esse fallback atende dataclasses, mas não reproduz automaticamente todo o protocolo genérico. Documente a limitação e mantenha testes em todas as versões suportadas.

Erros comuns

Os erros mais comuns são assumir que a operação faz cópia profunda, compartilhar listas mutáveis sem perceber, aceitar campos desconhecidos em __replace__, ignorar validações e depender do recurso em versões antigas sem fallback.

Outro problema é usar substituição para objetos que representam recursos vivos, como sockets ou transações. Nesses casos, criar uma “nova versão” pode não ter significado seguro.

Boas práticas

Prefira campos imutáveis quando o objeto será atualizado por substituição. Centralize invariantes no construtor. Rejeite nomes desconhecidos. Documente quais campos podem ser alterados. Meça desempenho quando houver muitas substituições em loops críticos.

Também vale manter funções de transformação pequenas. Uma função deve receber um estado e retornar outro, sem alterar argumentos globais.

Integração com outros recursos

Para aprofundar, consulte os conteúdos sobre copy no Python, dataclasses no Python, orientação a objetos e type hints.

A documentação oficial de copy e a referência de dataclasses descrevem o comportamento e as versões disponíveis.

Conclusão

copy.replace fornece uma interface clara para criar novas versões de objetos com alterações pontuais. Ele é especialmente valioso em modelos imutáveis, configurações, eventos, testes e fluxos concorrentes. O recurso não substitui cópia profunda e exige atenção a campos mutáveis, validações e compatibilidade. Quando usado com tipos bem definidos e invariantes centralizadas, torna mudanças de estado mais explícitas, seguras e fáceis de testar.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Código em tela representando t-strings no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    T-strings no Python 3.14: templates estruturados

    Aprenda t-strings no Python 3.14, entenda Template e Interpolation e crie renderizadores estruturados, extensíveis e mais seguros.

    Ler mais

    Tempo de leitura: 5 minutos
    16/10/2026
    CPU e módulos de memória representando shared_memory track no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    shared_memory track: controle o resource tracker

    Aprenda o parâmetro track de multiprocessing.shared_memory no Python para controlar o resource tracker e evitar remoções prematuras.

    Ler mais

    Tempo de leitura: 6 minutos
    16/10/2026
    Unidade de armazenamento de dados representando compactação com compression.zstd no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    compression.zstd: streams e compressão segura

    Aprenda compression.zstd no Python para compactar arquivos e dados com velocidade, streaming, dicionários e segurança.

    Ler mais

    Tempo de leitura: 6 minutos
    15/10/2026
    Código em tela para diagnóstico de processos com sys.remote_exec no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    sys.remote_exec: diagnostique processos Python

    Aprenda sys.remote_exec no Python para diagnosticar processos em execução com scripts controlados, segurança e boas práticas.

    Ler mais

    Tempo de leitura: 6 minutos
    15/10/2026
    Código Python e configuração de variáveis de ambiente com os.reload_environ
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    os.reload_environ: riscos com threads e ambiente

    Aprenda os.reload_environ no Python para atualizar variáveis de ambiente alteradas externamente, validar configurações e evitar problemas com threads.

    Ler mais

    Tempo de leitura: 5 minutos
    14/10/2026
    Código Python representando contextvars.Token e isolamento de contexto assíncrono
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    contextvars.Token: restaure contexto async

    Aprenda contextvars.Token no Python para definir valores temporários, restaurar contextos e isolar dados entre tarefas assíncronas.

    Ler mais

    Tempo de leitura: 6 minutos
    14/10/2026