Batu Lab NotesPractical developer guides

Replace one field in an immutable dataclass

By Batu ยท English technical notes

Also published in our Blogger archive.

Replace one field in an immutable dataclass

When a frozen dataclass represents a configuration value, changing it means creating a new value rather than assigning to the old one. dataclasses.replace() provides that operation for dataclass instances. The example starts with a retry policy of three attempts and a five-second delay, then requests a replacement with only delay_seconds=10.

replace() returns a new object of the same dataclass type. For initializer fields, values not named in the call are passed through from the source object; here, attempts remains 3. The assertions verify three separate facts: original still holds five seconds, revised has the expected two field values, and the two variables refer to different objects. The output 5->10 is a compact representation of that before-and-after result.

replace() calls the dataclass initializer for the returned object, so validation or computed setup in __post_init__() runs again. This also means init=False fields are an exception to the carry-forward behavior: they are not copied from the source object, but are initialized by __post_init__() if that method does so. A field name that is not a dataclass field raises TypeError; attempting to change an init=False field raises ValueError; and init-only values without defaults must be supplied. dataclasses.replace() has been available since Python 3.7. Its generic counterpart, copy.replace(), is newer and is not needed here.

The official dataclasses documentation documents replace(), initializer behavior, and its restrictions.

AI assistance disclosure: This article was drafted with AI assistance and should be reviewed where replacement triggers validation or side effects.

from dataclasses import dataclass, replace


@dataclass(frozen=True)
class RetryPolicy:
    attempts: int
    delay_seconds: int


original = RetryPolicy(attempts=3, delay_seconds=5)
revised = replace(original, delay_seconds=10)

assert original.delay_seconds == 5
assert revised == RetryPolicy(3, 10)
assert revised is not original
print(f"{original.delay_seconds}->{revised.delay_seconds}")
5->10