We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Modern Syntax and Modernisation step 10 of 18
Self-documenting f-strings: f"{x=}"
print(f"{user_id=}") is the single highest-value f-string feature for
day-to-day work, because it deletes the commonest debug-print bug there is: a
value printed under the wrong label after a copy-paste.
>>> total = 19.99
>>> f"{total=}"
'total=19.99'
>>> f"{total = }"
'total = 19.99'
Three rules worth memorising:
-
Whitespace inside the braces is preserved verbatim in the emitted text.
f"{a = }"givesa = 2. -
=usesrepr()by default — but supplying a conversion or a format spec switches it.f"{total=:.1f}"givestotal=20.0, formatted, not repr’d. -
The expression can be arbitrary:
f"{sum(xs)/len(xs)=}"emits the whole source textsum(xs)/len(xs)=followed by the value.
Rule 2 is why = defaults to repr: when you are debugging, you want to see
the quotes, the escapes, and the Decimal('1.10') wrapper. str is for
users; repr is for you.
What you are building
Two formatters plus the function under test.
def debug_repr(**values: object) -> str # "name=repr(value)", joined by ", "
def display(**values: object) -> str # "name=str(value)", joined by ", "
def solve(pairs: list[list[object]]) -> str # debug_repr + "\n" + display
solve receives an ordered list of [name, value] pairs, rebuilds them
into a dict[str, object], and splats that dict into each formatter. Both
formatters keep **values, so the **kwargs half of the exercise is intact —
what changes is where the order comes from.
The pair list is deliberate. The output order is part of the answer, and the
key order of a JSON object is not something you may rely on: a dict
preserves insertion order, but the insertion order of a mapping that arrived
over a wire is whatever the encoder and the store in the middle decided. A
list of pairs has exactly one order, and it is the one that was written down.
With [["n", 42], ["label", "it's"]] that is:
n=42, label="it's"
n=42, label=it's
The second line is what a naive f"{name}={value}" produces. Notice what it
costs you: label=it's and label="it's" are indistinguishable from
label= followed by the unquoted contents, and an empty string simply
vanishes.
Where this belongs, and where it does not
f"{x=}" is a debugging tool. Two rules for shipping it:
-
Never put an
=field in a user-facing string. The expression source is emitted literally, sof"Sorry, {self._internal_retry_budget=}"leaks your variable names into a UI. -
Never put one in a logging call.
logging.debug(f"{payload=}")formats eagerly even when DEBUG is off, and destroys the structuredrecord.argsyour aggregator groups on. Pass the value as an argument instead.
The decorator version is worth stealing for real code:
def trace_call[**P, R](fn: Callable[P, R]) -> Callable[P, R]:
@functools.wraps(fn)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
logger.debug("%s(%s)", fn.__name__, debug_repr(**kwargs))
return fn(*args, **kwargs)
return wrapper
— the label always matches the value, because there is only one place that writes the label.
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.