A generator suspends an operation at yield and resumes it when the consumer requests another value.
Python generators: lazy iteration does not make retained output free
Operation contract
The importer yields squared positive amounts one at a time. Turning the generator into a list materializes its results, and consuming it again produces no values because this generator instance has finished. A new call creates a new generator; keeping the same name does not reset the old one.
Failure and ownership boundary
Laziness defers work and failures until iteration. A generator reading a file can keep that file open while suspended, so its lifetime needs an owner rather than an assumption that exhaustion always happens. Python context managers: clean up on success and failure and Python asyncio TaskGroup: cancel sibling work and retain failure evidence keep termination paths visible.
Working program
def accepted_squares(amounts):
for amount in amounts:
if amount > 0:
yield amount * amount
stream = accepted_squares([3, -1, 4])
print(list(stream))
print(list(stream))Output
[9, 16]
[]Costs and limits
The generator retains iteration state rather than every result. Materializing k results costs O(k) output storage; this fixture still receives a pre-existing input list.
Common Mistakes
- One generator instance is not automatically reusable.
- Lazy iteration is not an end-to-end memory bound.
Connected lessons
Python lists: slicing copies the outer sequence, not nested objects, Python context managers: clean up on success and failure, Java streams: lazy pipelines and bounded results.
Apply this boundary
Python iterators: exhaustion and repeatable collection ownership, Python iterator interview: lazy calls, exhaustion and partial failure.
Check the next state boundary
Python itertools: adjacent groups and shared iterator consumption, Python async generators: close a suspended producer when leaving early.
Follow the service contract
Python generator send and close: suspension retains state until cleanup.
