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

    Estrutura de arquivos e código para pathlib.Path.info no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    pathlib.Path.info: cache de metadados de arquivos

    Aprenda pathlib.Path.info no Python para consultar tipos de arquivos com cache, iterar diretórios e evitar chamadas desnecessárias ao sistema.

    Ler mais

    Tempo de leitura: 7 minutos
    01/10/2026
    Notebook com material de testes em Python para loop_factory e asyncio
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    loop_factory: isole event loops em testes asyncio

    Aprenda loop_factory em IsolatedAsyncioTestCase para criar testes asyncio isolados, previsíveis e sem tarefas pendentes.

    Ler mais

    Tempo de leitura: 5 minutos
    30/09/2026
    Desenvolvedora navegando em arquivos ZIP com zipfile.Path no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    zipfile.Path: navegue em ZIPs sem extrair arquivos

    Aprenda zipfile.Path no Python para navegar, ler e validar arquivos dentro de ZIPs sem extrair tudo.

    Ler mais

    Tempo de leitura: 5 minutos
    30/09/2026
    Programador trabalhando com cabeçalhos de e-mail no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    email.headerregistry: cabeçalhos de e-mail seguros

    Aprenda email.headerregistry no Python para criar e analisar cabeçalhos, endereços, grupos, datas e parâmetros com segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    29/09/2026
    Terminal de computador usado para criar pseudoterminais com os.unlockpt no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    os.unlockpt: controle pseudoterminais no Python

    Aprenda os.unlockpt no Python para criar pseudoterminais, controlar subprocessos interativos e evitar erros de descritores.

    Ler mais

    Tempo de leitura: 6 minutos
    29/09/2026
    Código Python para gerenciamento de filas e threads com queue.ShutDown
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    queue.ShutDown: encerre filas e workers com segurança

    Aprenda queue.ShutDown no Python para encerrar filas com threads, liberar workers e evitar deadlocks.

    Ler mais

    Tempo de leitura: 6 minutos
    28/09/2026