We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Modelling Data with Dataclasses step 22 of 28
Convert and Validate a Port Number on Assignment
The last two exercises turned outside JSON into checked records. Once a
value is inside an object, though, code can still change it. Here you
convert a port number from outside input, then compare three classes that
hold it. Port is a frozen dataclass, LoosePort is a plain dataclass
that checks nothing, and GuardedPort checks every assignment.
solve(8080, "443")
# {"port": "Port(value=8080)", "port_frozen": "FrozenInstanceError",
# "loose": "LoosePort(value='443')", "guarded": "GuardedPort(value=443)",
# "hash_equal": True}
Use only the standard library.
Convert a port number
to_port(raw: object) -> int accepts an int or a string of digits and
returns a port from 1 to 65535. Leading zeros are fine, so "00443"
becomes 443. Check in this order:
- A
boolraisesTypeError("port must not be a bool"). - A string must pass
raw.isdecimal(), or raiseValueError(f"port is not numeric: {raw!r}").isdecimal()rejects signs, spaces, decimal points and superscripts such as"²". Convert a string that passes withint(raw), and let any error from that call propagate. - An
intstays as it is. Any other type raisesTypeError(f"port must be int or str, got {type(raw).__name__}"). - A number outside 1 to 65535 raises
ValueError(f"port out of range: {number}").
So the string "-1" is not numeric, and the integer -1 is out of range.
Write three classes
Portis a frozen dataclass withslots=Trueand one field,value: int. Add a classmethodparse(raw: object)that callsto_portand builds aPortfrom the result. Assigning tovalueraisesFrozenInstanceError, and equal ports have equal hashes. CallingPort(...)directly skipsparse, so onlyparsechecks the value.LoosePortis a plain dataclass withvalue: intand no checks. Python does not check annotations at runtime, so code can store a string in it.GuardedPortis a plain class with__slots__ = ("value",)and the annotationvalue: int. Its__init__accepts anyobject. Write a__setattr__that runs every assignment tovaluethroughto_portand stores the result withobject.__setattr__. For any other name, raiseAttributeError(f"unknown attribute {name!r}"). ItsreprisGuardedPort(value=<the int>).
Python calls __setattr__ for every obj.name = value, including the
assignment in your own __init__. Inside __setattr__, the line
self.value = ... would call __setattr__ again, without end. That is
why you store through object.__setattr__. Because to_port raises
before you store anything, a rejected assignment keeps the old value.
What solve returns
solve(initial, reassign) returns a dict with five keys. The starter's
helpers _frozen_error, _loose_repr and _hash_equal already compute
three of them.
| Key | Value |
|---|---|
"port" |
repr(Port.parse(initial)), or the conversion error |
"port_frozen" |
"FrozenInstanceError" after you try to assign reassign to the port's value, or "" if parsing failed |
"loose" |
The repr of LoosePort(0) after you assign reassign to value. It ignores initial. |
"guarded" |
The repr of GuardedPort(initial) after you assign reassign to value, or the first conversion error. If the constructor fails, skip the assignment. |
"hash_equal" |
Whether Port.parse(8080) and Port.parse("8080") are equal and have equal hashes |
Format a caught TypeError or ValueError as
"<class name>: <message>", such as "ValueError: port out of range: 0".
Compute each key on its own, so a bad initial does not stop the loose
and hash_equal keys.
Assign reassign with setattr(instance, "value", reassign). mypy
rejects guarded.value = reassign, because reassign is an object and
the attribute is an int. Your __setattr__ accepts any input, but mypy
checks the annotation.
Going further
The attrs library does this for you, with field(converter=...) and
on_setattr hooks. A method such as set_value(raw: object) is another
choice, and it shows the conversion in its signature.
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.