We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Generators, Iterators and Streaming Data step 5 of 9
Parse Records in Batches with a Generator
A job reads a long stream of name:count lines, such as alpha:7, and sends
the records on in batches. The stream can be too big for a list, so parse it
lazily. Read a line only when the caller asks for the batch that needs it. The
supplied CountingSource counts every line your code reads, so the tests can
check when the reading happens.
batches = parse_records(["a:1", "b:2", "c:3"], chunk=2) # reads nothing yet
next(batches) # [Record(name='a', count=1), Record(name='b', count=2)]
next(batches) # [Record(name='c', count=3)]
Write parse_line
parse_line(line) turns one line into a Record.
- Split the line at its first
:. The part before it is the name, and the part after it is the count. - The name must not be empty. Keep it exactly as written, spaces included.
- The count must pass
str.isdecimal(). That allows leading zeros ("alpha:007"gives 7) and decimal digits from other writing systems ("x:٣"gives 3). It rejects an empty count, signs, spaces, decimal points, a second colon and superscripts such as"x:²". - Convert the count with
int(). Ifint()still raises, for example on a count thousands of digits long, raise the same error as for any bad line. - For any bad line, raise
ValueError(f"malformed record: {line!r}"). For example,":7","x:-1"and"x:1:2"are all malformed.
Write parse_records
parse_records(lines, *, chunk) is a generator function. It yields lists of
records in input order.
- Calling it must not read any line.
- If
chunk <= 0, raiseValueError(f"chunk must be positive, got {chunk}")before you read any line. Put this check inside the generator body. It then runs at the firstnext(), not at the call. This is the opposite ofbulk_pagesin T5, and it means a caller who never asks for a batch never sees the error. - Yield a batch as soon as it holds
chunkrecords. Read no line beyond the batch the caller asked for. - When the lines run out, yield the last short batch if it holds any records. An empty source yields no batches, not one empty batch.
- Start a new list for each batch.
- When
parse_lineraises on a bad line, let itsValueErrorend the generator. Batches you already yielded stay delivered. The unfinished batch never reaches the caller.
Write solve
solve runs parse_records over the lines and reports what happened.
- Wrap the lines in a
CountingSource. Passlinesitself, oritertools.cycle(lines)whenrepeatis true.cyclerepeats the lines forever, so the tests always pass atakewhenrepeatis true. An empty list stays empty undercycle. - Call
parse_records(source, chunk=chunk), and recordsource.pulledright away aspulled_before_first. - If
takeis notNone, read at mosttakebatches, usingitertools.islice(batches, take). Withtake=0,islicenever callsnext(), so the generator body never runs. That is whychunk=0, take=0gives no error. - Loop over the batches inside one
tryblock. CatchValueErrorand storestr(exc)aserror.
solve returns these keys:
| Key | Meaning |
|---|---|
pulled_before_first |
source.pulled right after the call to parse_records. Always 0. |
batch_sizes |
The length of each batch the loop received |
head |
The first four records of the first batch, as "name=count" strings |
total |
The sum of the counts in the batches the loop received |
pulled |
source.pulled at the end. It counts every line read, including a malformed line and the lines of the unfinished batch. |
error |
The ValueError message, or "" |
If no batch arrives, batch_sizes and head are empty and total is 0.
For lines=["a:1", "b:2", "bad"] and chunk=2, you get one batch of 2,
total 3, pulled 3 and the error "malformed record: 'bad'". Add take=1,
and your code never reads the bad line. Then pulled is 2 and there is no
error. With repeat=True, chunk=2 and take=3, valid lines give three
batches after exactly six reads.
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.