We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Stdlib Mastery step 27 of 55
singledispatch: a JSON encoder callers can extend from outside
Replace an isinstance ladder with an open, extensible dispatcher.
Build a module-level generic function:
@singledispatch
def to_json_value(value: object) -> object: ...
whose base implementation raises TypeError, with registered handlers for:
| type | result |
|---|---|
int |
the integer |
str |
the string |
Decimal |
str(value) |
datetime |
value.isoformat() |
list |
each element converted, as a list |
dict |
{str(key): converted} |
Then:
def solve(specs: list[str], register_extra: bool) -> list[object]:
Each spec is "<kind>:<payload>", built into a Python object and passed
through to_json_value; append the result, or the string "TypeError" if it
raises.
| spec | object |
|---|---|
"int:42" |
42 |
"str:hello" |
"hello" |
"bool:true" / "bool:false" |
True / False |
"decimal:1.50" |
Decimal("1.50") |
"datetime:<iso>" |
datetime.fromisoformat(iso) |
"list:1,2,3" |
[1, 2, 3] (ints; empty payload gives []) |
"mixed:" |
[1, Decimal("0.25"), True] |
"dict:a=1,b=2" |
{"a": 1, "b": 2} (int values) |
"money:250" |
a Money class defined inside solve |
| anything else |
a Mystery class defined inside solve, never registered |
When register_extra is true, register a handler for Money returning
{"cents": value.cents} — from inside solve, without touching the
module-level definitions. That is the whole point of the exercise: a caller
who cannot edit your source can still teach your encoder a new type.
Note "bool:true" — bool is a subclass of int, so it dispatches to the
int handler through the MRO with no extra registration. That is dispatch
doing the work the ladder’s branch ordering used to do by hand.
What you are giving up, and it is the lesson. Dispatch is a runtime
mechanism, so no checker can prove your coverage is complete:
to_json_value(object()) type-checks and raises. The closed alternative —
match over a closed union with typing.assert_never — can be proven
exhaustive, and mypy will name the case you forgot when you extend the union.
Open extension and static exhaustiveness are mutually exclusive; pick per use
case.
A registration detail. register keys the registry on a real class, so
an annotation of list[int] raises at import time — list[int] is a
GenericAlias, not a type, and dispatch only ever sees the erased list
anyway. Register the bare list with the explicit two-argument form so the
implementation can still carry a precise annotation that survives
--disallow-any-generics.
Loading visualization…
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.