We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Modern Syntax and Modernisation step 18 of 18
The walrus operator, tastefully
:= earns its keep in exactly three shapes. Outside them it is usually a way
to make a line denser without making it clearer — and PEP 572’s own advice is
blunt about that: “If either assignment statements or assignment expressions
can be used, prefer statements.”
Shape 1 — capture the value you just tested
if (found := ENTRY_RE.match(line)) is not None:
level = found["level"]
Without it you either call .match() twice, or you assign then test on two
lines and lose the coupling. Under mypy --strict there is a third benefit:
the walrus narrows Match[str] | None to Match[str] inside the branch, so
you do not need an assert found is not None to silence the checker. That
assert is one of the most common pieces of noise in strictly-typed code, and
this deletes it.
Shape 2 — read until sentinel
while block := stream.read(size):
process(block)
The pre-walrus form needs a priming read before the loop and a second read at the bottom — two call sites that must stay in sync, and the classic place for an off-by-one when someone changes the chunk size.
Shape 3 — comprehension filtering
[compute(x) for x in xs if compute(x) > 0] # compute() runs TWICE per item
[y for x in xs if (y := compute(x)) > 0] # once
This is not a micro-optimisation. When compute hits a database, charges an
API, or is simply non-deterministic, double evaluation is a correctness
bug — the value you filtered on is not necessarily the value you emitted.
The scoping rule behind it is deliberate and occasionally surprising: a walrus
target inside a comprehension binds in the containing scope, not the
comprehension’s own. That is what makes the value available in the element
expression — and also what lets a comprehension quietly clobber a local called
y.
What you are building
Four functions, then solve wiring them together.
-
parse_log(lines) -> list[tuple[str, str]]— for each line, strip it and matchENTRY_RE(given: an uppercase level, whitespace, a non-empty message). Non-matching lines are skipped. Return(level, message)pairs. A tuple, not a list — the grader compares container types. -
chunks(stream: BinaryIO, size: int) -> Iterator[bytes]— yield successive reads until the stream is exhausted. -
select_expensive(items, key, prefix) -> list[str]— one comprehension returning the computed keys that start withprefix.keymust run exactly once per item. -
dedupe_expensive(items, key) -> list[T]— keep the first item per key, preserving order.keymust run exactly once per item.
solve builds the BytesIO from blob, wraps the key function in a counter,
and returns entries, chunks (as lengths), selected, deduped,
select_calls and dedupe_calls. The call counts are asserted: a double
evaluation is a test failure, not a style note.
The idiom that --strict rejects
The famous one-line dedupe is:
[x for x in xs if (k := key(x)) not in seen and not seen.add(k)]
mypy --strict rejects it: "add" of "set" does not return a value (it only ever returns None) [func-returns-value]. That check exists for a good reason
and this is a genuine abuse of it. Which is why dedupe_expensive here is a
statement loop with the walrus in the if — the readable form, the checkable
form, and still one key call per item.
Loading visualization…
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.