copy.replace constructs a new supported record with selected fields changed, retaining other field references.
Python copy.replace: revise one field without cloning owned data
Operation contract
A frozen invoice record is revised from pending to approved. The new record is separate, while its tuple of line IDs remains the same object. That sharing is safe here because the tuple is immutable. The generic replace function works with dataclasses and other types that expose the replacement protocol.
Failure boundary
Replacement is shallow. If a record contains a mutable list or dictionary, old and new records can still observe changes to that nested object. Frozen dataclasses prevent assigning their fields, not mutation inside referenced objects. Validate business transitions separately; replace does not enforce an approval workflow.
Working program
from copy import replace
from dataclasses import dataclass
@dataclass(frozen=True)
class InvoiceRevision:
invoice_id: str
status: str
line_ids: tuple[str, ...]
pending = InvoiceRevision("inv-47", "pending", ("line-47", "line-48"))
approved = replace(pending, status="approved")
print("old", pending.status)
print("new", approved.status)
print("shared_immutable_lines", pending.line_ids is approved.line_ids)Output
old pending
new approved
shared_immutable_lines TrueCosts and limits
A new outer record is allocated. Unchanged nested values are referenced rather than recursively copied, avoiding a full graph traversal.
Common Mistakes
- A frozen outer record can still refer to mutable nested data.
- replace does not accept every arbitrary class; it needs a supported replacement protocol.
- Field replacement does not certify a valid business state transition.
