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.







