Skip to content

← Pattern Matching, f-Strings and Newer Syntax step 15 of 23

Medium End-to-End

Reuse a Computed Value with the Walrus Operator

Code often computes a value, tests it, and then needs the same value again. The walrus operator := lets you keep the value you compute inside the test. name := expression stores the result in name and also returns it. Here you use it in an if, a while loop and a list comprehension.

# In an if: match once, then use the match.
if (found := ENTRY_RE.match(line.strip())) is not None:
    level = found["level"]

# In a while loop: read and test in one line.
while block := stream.read(size):
    process(block)

# In a comprehension: call compute once per item.
results = [y for x in xs if (y := compute(x)) > 0]

The is not None check tells mypy that found is a match, not None, inside the branch. Without :=, the comprehension becomes [compute(x) for x in xs if compute(x) > 0], which calls compute twice for every item it keeps. A name you assign with := in a comprehension stays set in the enclosing function afterward, so pick one that no other variable uses.

What to write

Fill in four helpers and solve. Your submission must pass mypy --strict.

  • parse_log(lines) strips each line and calls ENTRY_RE.match on it once. Skip lines that don't match. Return a list of (level, message) tuples in input order, with the message exactly as the pattern captures it, so "TRACE spaced out" gives ("TRACE", "spaced out").
  • chunks(stream, size) reads size bytes at a time until read returns the empty b"". It is a generator, and each yield hands one block to the caller. Don't yield the empty block, and don't close the stream, because it belongs to the caller. The last block can be shorter than size.
  • select_expensive(items, key, prefix) returns each key(item) that starts with prefix, in order, duplicates included. Use one list comprehension, and call key exactly once per item, including items that fail the test. In def select_expensive[T](...), T stands for whatever item type the caller passes.
  • dedupe_expensive(items, key) returns the first original item for each distinct key(item). Call key exactly once per item, duplicates included. Use a for loop and a set of the keys seen so far.

items can be any iterable, so loop over it only once.

Build the report in solve

Wrap blob in io.BytesIO and pass it to chunks. Make two key functions, one for select_expensive and one for dedupe_expensive. Each returns value.strip().lower() and counts its own calls. Pass prefix unchanged, so a prefix of "A" matches none of the lowercase keys. Return a dictionary with these keys:

Key Value
entries The list from parse_log(lines)
chunks The length of each block, in order
selected The list from select_expensive
deduped The list from dedupe_expensive
select_calls How many times the key function for select_expensive ran
dedupe_calls How many times the key function for dedupe_expensive ran

For items ["Apple", " apple", "beta"] and prefix "a", selected is ["apple", "apple"], deduped is ["Apple", "beta"], and both counts are 3.

Edge cases

  • An empty prefix matches every key, including an empty string.
  • Empty items give empty lists and zero key calls.
  • An empty blob gives no blocks, after one read that returns b"".
  • size is always at least 1.