An iterator returns itself from iter() and supplies successive values through next(), raising StopIteration when exhausted.
Python iterators: exhaustion and repeatable collection ownership
Operation contract
The receipt batch retains an immutable tuple snapshot. Each call to iter(batch) creates a fresh iterator, so separate readers can traverse the batch independently. Holding one iterator and converting it to a list twice instead consumes it once and then produces an empty result. An iterable collection and a one-use iterator are different public contracts.
Failure and ownership boundary
The tuple snapshot protects only the outer sequence. This fixture restricts receipt IDs to integers; accepting mutable receipt objects would retain their aliases. An iterator that reads a database cursor also owns a live I/O lifetime and cannot be treated like this in-memory batch. Python generators: lazy iteration does not make retained output free and Python context managers: clean up on success and failure explain that distinction.
Working program
class ReceiptBatch:
def __init__(self, receipt_ids):
candidate = tuple(receipt_ids)
if any(type(identifier) is not int or identifier <= 0 for identifier in candidate):
raise ValueError("positive receipt IDs required")
self._receipt_ids = candidate
def __iter__(self):
return iter(self._receipt_ids)
batch = ReceiptBatch([41, 42])
reader = iter(batch)
print(list(reader))
print(list(reader))
print(list(batch))Output
[41, 42]
[]
[41, 42]Costs and limits
Creating the tuple snapshot costs O(n) time and storage; creating an iterator is bounded. Materializing its remaining values into a list takes O(r) retained output.
Common Mistakes
- Do not promise repeatable reads when returning the same iterator instance.
- An outer tuple does not freeze mutable values stored inside it.
Connected lessons
Python generators: lazy iteration does not make retained output free, Python tuples: immutable containers can still contain mutable state, Python iterator interview: lazy calls, exhaustion and partial failure.
Check this related boundary
Python iterator reference: consume once or create a repeatable source.
Trace the related workflow
Python iter(callable, sentinel): stop a pull loop at a declared marker.
Trace the next boundary
Python zip(strict=True): reject a mismatched pair of input feeds, Python itertools.tee: a lagging reader retains buffered values.
