# Why does this Python total work for a list but fail for a generator?

> Review a validation pass that exhausts a generator, then compare a one-pass Python repair using executable counterexamples.

- Canonical HTML: [https://unrust.dev/guides/python-iterator-consumption-challenge](https://unrust.dev/guides/python-iterator-consumption-challenge)
- Markdown representation: [https://unrust.dev/guides/python-iterator-consumption-challenge/index.md](https://unrust.dev/guides/python-iterator-consumption-challenge/index.md)

Published: 2026-10-21
Published by Unrust. Scoring and editorial method: https://unrust.dev/help#methodology

## Direct answer

The generator expression passed to any reads the underlying iterable. Because both quantities are non-negative, it reaches the end while looking for a true condition. A list can be traversed again, but a generator cannot be restarted by wrapping it in another expression. The repair validates and accumulates in one pass. It also avoids materializing an arbitrarily large input.

## Read the contract before the patch

Accept a finite iterable of rows containing integer quantity values. Reject a negative quantity with ValueError and otherwise return the sum. A list and a one-shot generator containing the same rows must give the same result.

Predict the result before reading the repair. Use a small counterexample to separate the code’s behavior from the behavior you expected.

### Original teaching example

```python
def total_quantity(lines):
    if any(row["quantity"] < 0 for row in lines):
        raise ValueError("negative quantity")
    return sum(row["quantity"] for row in lines)
```



## Compare the repair against the same inputs

The generator expression passed to any reads the underlying iterable. Because both quantities are non-negative, it reaches the end while looking for a true condition. A list can be traversed again, but a generator cannot be restarted by wrapping it in another expression. The repair validates and accumulates in one pass. It also avoids materializing an arbitrarily large input.

### Repair under the stated contract

```python
def total_quantity(lines):
    total = 0
    for row in lines:
        quantity = row["quantity"]
        if quantity < 0:
            raise ValueError("negative quantity")
        total += quantity
    return total
```

**Execution-checked examples, 21 October 2026**

| Input | Original | Repair |
| --- | --- | --- |
| List containing 2 and 5 | 7 | 7 |
| Generator yielding 2 and 5 | 0 | 7 |
| Empty generator | 0 | 0 |
| Generator yielding 2 and -1 | ValueError | ValueError |



## Extend the review

The contract assumes finite input and integer quantities. This example does not validate row schemas, handle an infinite iterable or roll back external side effects.

Change one assumption, predict a new outcome, then run the example in your own environment. The public lab repository contains the snippets and their checks. These examples teach review reasoning; they do not measure coding ability.

- [All public lab challenges](https://unrust.dev/guides/public-code-review-challenges/index.md)

- [Runnable lab source and tests](https://github.com/yomariano/unrust-code-review-lab)
- [Download the teaching pack](https://unrust.dev/resources/code-review-lab.json)

## Make your prediction

For a generator yielding quantities 2 and 5, what does the original function return?

- Zero. The validation pass consumes the non-negative generator.
- Seven. A new generator expression restarts the underlying input.

**Reasoning:** The generator expression passed to any reads the underlying iterable. Because both quantities are non-negative, it reaches the end while looking for a true condition. A list can be traversed again, but a generator cannot be restarted by wrapping it in another expression. The repair validates and accumulates in one pass. It also avoids materializing an arbitrarily large input.

## Practice loop

1. State the input contract in your own words.
2. Predict each boundary result before running the code.
3. Compare the original and repair with exactly the same input.
4. Write one additional case and explain what it establishes.

## Common questions

### Is this a scored Unrust assessment?

No. This is a fully public, worked teaching example. Its answers are intentionally available and it produces no diagnostic score.

### Can I reuse the example in a tutorial?

Yes. The public lab source is MIT licensed. Keep the license, state the input contract and link to the example and its verification date.

## Limitations

The contract assumes finite input and integer quantities. This example does not validate row schemas, handle an infinite iterable or roll back external side effects.

## Provenance

Original public teaching material authored for Unrust and execution-checked on 21 October 2026. It is separate from the diagnostic and Daily puzzle banks.

## Sources

- [Iterators in the functional programming HOWTO](https://docs.python.org/3/howto/functional.html#iterators) — Python documentation. Iterators advance through a stream; exhausting one does not reset it.

## Related guides

- [Free code review challenges with explanations and runnable examples](https://unrust.dev/guides/public-code-review-challenges/index.md): Practice three small JavaScript, Python and TypeScript code reviews. Predict the behavior, inspect a repair and run the original public teaching examples.
- [How do you read code against its contract?](https://unrust.dev/guides/reading-code-against-a-contract/index.md): Learn how to compare implementation with intent, find mismatched assumptions, and catch bugs that clean-looking code can hide.
- [How do you review AI-generated code?](https://unrust.dev/guides/reviewing-ai-generated-code/index.md): Review a JavaScript bug that mishandles zero, compare the corrected code and boundary tests, and learn how to verify AI-generated changes against a contract.

Next step: [Take the free diagnostic](https://unrust.dev/diagnostic/index.md)
