Batch an iterator without losing the tail
Batching reduces overhead, but a final short batch must not disappear. An iterator may be consumed only once, so accumulate at most one batch at a time. Yielding the current list and then allocating a fresh one avoids changing batches the caller already received. Reject a zero batch size instead of looping forever.
from itertools import islice
source = iter(range(5))
print(list(islice(source, 2)))Think before coding
The generator retains at most one batch beyond the source iterator. A fresh list prevents aliasing between results, and the final condition preserves the incomplete tail without adding an empty batch. Work through the example by hand before changing the starter. Decide what should happen for an empty input, a single value and a repeated value where those cases apply. The checks use several inputs so a hard-coded answer cannot stand in for the rule.
Your task
Write chunks(values, size) as a generator of lists, each containing at most size items. Keep the final partial batch. For size <= 0, iteration must raise ValueError.
The explanation opens here once every check passes.