We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Concurrency Without Regret step 13 of 38
A Fetcher Protocol three implementations can satisfy
Declare a Protocol that three different implementations can satisfy — then
consume it.
class Fetcher[T](Protocol):
def fetch(self, key: str) -> ???: ...
async def collect[T](fetcher: Fetcher[T], keys: Sequence[str]) -> dict[str, T]:
collect fetches every distinct key exactly once, in first-seen order, and
returns the mapping. Duplicates in keys must not produce a second call — the
report’s calls log is what proves it.
The real exercise is one line
The starter declares the Protocol as
async def fetch(self, key: str) -> T: ...
and it does not type-check, because the driver’s registry is annotated
dict[str, Fetcher[bytes]] and contains three implementations:
-
AsyncFetcher— a coroutine function. Fine either way. -
FutureFetcher— a plaindefthat resolves aFutureimmediately. This is what a cache looks like: no suspension when the answer is already known. -
TaskFetcher— a plaindefthat schedules the work and returns theTask.
An async def in a Protocol demands that every implementation be a coroutine
function, forever. A plain method returning an Awaitable[T] demands only that
what comes back can be awaited — which a coroutine object, a Future and a
Task all can.
That is not a stylistic preference. It is the difference between a plug-in point that can later grow a cache or a request batcher, and one that cannot without a breaking change to your published interface.
What the report proves
-
payloads— the awaited values, asbytes. If you forget theawait, what lands in the dict is a coroutine object or aFuture, and the comparison fails loudly. (--strictalone would not have told you: theunused-awaitableerror code is opt-in.) -
calls— one entry perfetchcall, in order. Dedup happens before the call, not after. -
order— the mapping’s key order is first-seen order.
Where the type system earns its keep
Fetcher[T] is generic in what it produces, so collect returns
dict[str, T] rather than dict[str, Any], and a Fetcher[bytes] gives you
dict[str, bytes] all the way to the caller. Under --disallow-any-generics a
bare Awaitable is rejected — parameterise it — and under --strict the
accumulator needs its own annotation, because an empty dict has no inferable
value type.
Loading visualization…
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.