We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Laziness, Iteration and Pipelines step 10 of 14
Iterator, Iterable, Generator, Sequence — and what the annotation promises
An annotation on a parameter is a promise about what you will do to the argument, not a description of what the caller happens to pass.
| Annotation | What you are promising |
|---|---|
Iterable[T] |
I will iterate it — possibly zero times, at most once |
Iterator[T] |
I will call next() on it; I know it is single-shot and I own its position |
Sequence[T] |
I may take len(), index it, and traverse it more than once |
Generator[Y, S, R] |
I may send() into it and I care about its return value |
Consuming an Iterable[T] twice works for a list argument and silently returns
nothing on the second pass for a generator. No exception, no warning — the
second consumer just sees an empty stream. Annotating Iterable while
traversing twice is how that bug ships.
Generator[Yield, Send, Return] takes three parameters; AsyncGenerator[Yield, Send] takes two. Since 3.13 (PEP 696) the trailing parameters default to
None, so Generator[int] means Generator[int, None, None] — but a library
that still supports 3.12 must write the full three-parameter form. And prefer
collections.abc.Iterator over the deprecated typing.Iterator alias.
What to write
Only resolve_all. Everything else — the source builders, the instrumentation,
the driver — is provided.
async def resolve_all[T](
sources: Sequence[Awaitable[T] | Callable[[], Awaitable[T]] | T],
) -> list[T]
Normalise a heterogeneous sequence into a list of plain values, preserving order:
-
a bare
Tpasses straight through, -
an
Awaitable[T](a coroutine object, aTask, aFuture) is awaited, -
a zero-argument
Callable[[], Awaitable[T]]is invoked and its result awaited.
The parameter is Sequence, not Iterable, and that is a design decision, not
a formality: the contract is “one result per input, in input order”, which is a
claim you can only honestly make about something with a length.
Where the type system earns its keep
Two narrowing steps, and the order matters:
-
callable(source)peels off the factory arm. Check it first — a coroutine object is not callable, and neither is aTask, so nothing that is already awaitable can be misrouted into the factory branch. -
isinstance(pending, Awaitable)peels off the awaitable arm.collections.abc.Awaitableis genuinely runtime-checkable: its__subclasshook__looks for__await__.
No # type: ignore, no typing.cast, no Any anywhere in the signature.
Getting mypy to accept this without an escape hatch is the exercise.
What the report proves
-
values— results in input order. Completion order is deliberately not input order in several cases. -
started— the values whose_producebody actually began, in order. This is the payoff. A coroutine object does nothing until awaited; aTaskis already scheduled and runs the moment the loop gets control; a factory has not even built its coroutine yet. -
calls— the values whose factory was invoked, in order. -
pending— outstanding tasks at the end. Must be0.Loading visualization…
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.