We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Concurrency with Threads and asyncio step 32 of 56
Batch an Async Stream and Close Its Source
Write an async version of itertools.batched. batched_stream(source, size)
reads an async generator and yields lists of size items. It checks size
as soon as you call it. It closes source when it stops, whether the source
runs out, fails, or the caller closes the stream early.
# source is an async generator that yields 1 to 7
async with aclosing(batched_stream(source, 3)) as batches:
async for batch in batches:
print(batch) # [1, 2, 3], then [4, 5, 6], then [7]
What to write
batched_stream(source, size) is a plain def, so its check runs at the
call:
- If
size <= 0, raiseValueError(f"size must be positive, got {size}")straight away. Don't read or closesource. - Otherwise return
_batched(source, size). Return the generator itself, not a coroutine the caller has to await.
This is the same split as bulk_pages. A
check in the body of an async generator would run only at the first anext().
_batched(source, size) is the async generator that does the work:
- Read
sourcein order insideasync with aclosing(source). Then the source closes when_batchedfinishes or raises, and when the caller closes_batched. - Yield a new list each time you have
sizeitems. - When the source runs out, yield the leftover items as one shorter list, if there are any. An empty source yields nothing.
- Read no further ahead than the batch you are filling.
- If the source raises while you fill a batch, let the error propagate and don't yield the partial batch. The caller keeps the batches it already got.
The yield inside async with aclosing(...) is safe. aclosing sets no
timer and cancels nothing, so it is not a cancel scope. Keep timeouts and
TaskGroups out of this generator, as
the previous article
explains.
What the driver reports
Keep _source, _drive, solve and Report as they are. _source yields
values in order and logs "pull" for each value it hands out. When the
index of the next value equals fail_at, it raises
RuntimeError(f"source failed at {index}") instead. A fail_at of None,
a negative number or a number past the end never fails. The source's
finally logs "closed", so once it has started it logs that even for an
empty list.
_drive loops over your stream inside async with aclosing(stream). If the
test sets stop_after_batches, the driver stops once it has that many
batches. It catches your ValueError and the source's RuntimeError.
batches: the batches the driver received.pulled: how many values the source handed out, counting any in a dropped partial batch.source_closed: whether the source logged"closed".error: the message of theValueErrororRuntimeError, or"".
Some examples:
- Seven values with
size=3andfail_at=4give one batch,[1, 2, 3]. The source hands out four values. The fourth goes nowhere, because its batch never filled. The source closes, anderroris"source failed at 4". - With
size=2andstop_after_batches=1, the source hands out two values and closes beforesolvereturns. - With
size=0there are no batches,pulledis0,source_closedisFalseanderroris"size must be positive, got 0". - With a valid size,
source_closedisTrueeven for an empty list or a failure at index 0, because_batchedstarts the source.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Python reference solution
Sign in to track your attempts toward unlocking the reference solution.