We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Modelling Data with Dataclasses step 17 of 28
Update a Frozen Record with replace()
A thermometer reports in Celsius or Kelvin. Your frozen Reading record
takes the number and its scale, and works out Fahrenheit in
__post_init__. The scale is an InitVar, which you met in the exercise
Compute Cents from an Amount with __post_init__. The record uses it once
and does not store it. Now copy a Kelvin reading with dataclasses.replace
and change nothing:
solve(373.15, "k", {}, 5, 3)
# {"error": "", "celsius": 373.15, "fahrenheit": 703.67,
# "base_fahrenheit": 212.0, "duration": 3}
The copy says 703.67 degrees Fahrenheit instead of 212. replace builds
the copy by calling __init__ with the stored fields, and __init__ runs
__post_init__ again. The record never stored the scale, so
__post_init__ gets the default, "c", and reads 373.15 as Celsius.
Passing {"scale": "k"} as the change keeps the Kelvin meaning.
This exercise shows the bug on purpose. In your own code, store the scale
as a real field, or convert to one unit in __post_init__ and name the
field after that unit. The exercise needs Python 3.13 or newer, for
copy.replace.
Complete Reading
- Keep
Readingfrozen. Declarecelsius: float, thenscale: InitVar[str] = "c", thenfahrenheit: float = field(init=False, default=0.0). - Write
__post_init__(self, scale: str) -> None, and computefahrenheitfrom this table:
| scale | fahrenheit |
|---|---|
"c" |
self.celsius * 9.0 / 5.0 + 32.0 |
"k" |
(self.celsius - 273.15) * 9.0 / 5.0 + 32.0 |
| anything else | raise ValueError(f"unknown scale: {scale!r}") |
- Store the result with
object.__setattr__, because the record is frozen. celsiuskeeps the number exactly as given, for either scale. Do not convert or overwrite it.- Inputs are finite numbers. You do not need to check that a temperature is possible.
Describe the changes with a TypedDict
EvolveArgs lists the changes that evolve accepts. Declare it as a
TypedDict with total=False, which makes every key optional. Give it two
keys, celsius: float and scale: str. An empty dictionary is a valid
set of changes.
Write evolve
evolve(obj, changes) returns dataclasses.replace(obj, **changes).
- If
changeshas a"fahrenheit"key, raiseTypeErrorbefore you callreplace.__post_init__computesfahrenheit, so callers must not set it. - Do not change
objorchanges. - Any other unknown key reaches
__init__, which raisesTypeError.
Keep the EvolveArgs type on the parameter. solve passes the runtime
dictionary in with cast(EvolveArgs, changes). A cast only tells mypy what
type to assume, and it does not check or remove keys. So the tests can
still send keys such as "bogus".
Add replace to Duration
copy.replace(obj, **changes) makes a changed copy of any object that has
a __replace__ method. Dataclasses get one from the decorator. Duration
is a plain class, so you write it:
__replace__(self, /, **changes: int) -> Durationreturns a newDurationwhose seconds arechanges.get("seconds", self.seconds). The original stays unchanged.- It must handle no changes and a
secondschange. No other keys reach it. Accept any integer, including a negative one.
Write solve
- Build
base = Reading(celsius, scale). - Call
evolve(base, cast(EvolveArgs, changes)). - If step 1 or 2 raises
TypeErrororValueError, put the exception's class name, such as"TypeError", in"error". Return0.0for the three temperature keys and0for"duration", even if step 1 succeeded. - Otherwise, call
copy.replace(Duration(duration_seconds), seconds=duration_change)and return:
| Key | Value |
|---|---|
"error" |
"" |
"celsius" |
evolved.celsius |
"fahrenheit" |
evolved.fahrenheit |
"base_fahrenheit" |
base.fahrenheit |
"duration" |
the new Duration's seconds |
Do not round the temperatures. Your code must pass mypy --strict.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Python reference solution
Sign in to track your attempts toward unlocking the reference solution.