Skip to content

Commit 415de13

Browse files
committed
Add iterator stream examples
1 parent b8c5684 commit 415de13

14 files changed

Lines changed: 282 additions & 59 deletions

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/en/1.1.0
2222
- Markdown-backed example sources with `:::program` and `:::cell` blocks.
2323
- Example source verifier, formatter, embedded-source build step, and golden parity check.
2424
- GitHub Actions verification workflow.
25+
- `iterators` and `generator-expressions` examples to make the Iteration arc explicit.
2526

2627
### Changed
2728

2829
- Example walkthroughs now pair prose, source fragments, and output evidence.
30+
- Iteration examples now frame Python iteration as a value-stream protocol: producers, consumers, eager containers, lazy streams, and one-pass iterators.
2931
- Read-only code highlighting is handled by Shiki; editable code highlighting is handled by CodeMirror.
3032
- Prototype routes under `/layout-options/*` bypass the Worker Cache API and return `Cache-Control: no-store`.
3133
- Static assets use immutable cache headers only on fingerprinted filenames.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Production: <https://www.pythonbyexample.dev> (`workers.dev` remains enabled as
66

77
## Features
88

9-
- 50 curated Python 3.13 examples in learning order
9+
- 52 curated Python 3.13 examples in learning order
1010
- Literate source/output cells for each example walkthrough
1111
- Editable complete examples powered by CodeMirror
1212
- Read-only syntax highlighting powered by Shiki

docs/example-quality-rubric.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Release gates outside the score:
2222
- page layout remains restrained and readable
2323
- examples verify under the configured Python version
2424
- generated embedded source and asset manifests are up to date
25+
- iteration examples identify what produces values, what consumes values, whether values are stored or streamed, and whether the stream is reusable or one-pass
2526

2627
Quality bands:
2728

@@ -44,6 +45,8 @@ Flag these during review even when the code is correct:
4445
- Data is purely toy-shaped when realistic small data would clarify the purpose.
4546
- Notes repeat the prose instead of adding practical guidance.
4647
- The program shows valid syntax but not when or why to use it.
48+
- An iteration example uses a lazy object but does not show when values are consumed.
49+
- An iteration example blurs eager containers with one-pass streams.
4750

4851
## Strengthening checklist
4952

@@ -55,3 +58,4 @@ Before publishing or substantially editing an example, ask:
5558
4. Does the example use small realistic data?
5659
5. Is there a contrast readers commonly need to avoid misuse?
5760
6. Would an explicit loop, named function, or mutation-vs-copy contrast make the idiom clearer?
61+
7. For iteration examples, what produces values, what consumes them, and are they stored eagerly or streamed lazily?

src/asset_manifest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# Generated by scripts/fingerprint_assets.py. Do not edit by hand.
22
ASSET_PATHS = {'SITE_CSS': '/site.06e133dcde6a.css', 'SYNTAX_JS': '/syntax-highlight.3b6c7f730d46.js', 'EDITOR_JS': '/editor.dd81f5171b14.js'}
3-
HTML_CACHE_VERSION = '36c5175ef57c'
3+
HTML_CACHE_VERSION = '559146e23c49'

src/example_sources/comprehensions.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ summary = "Comprehensions build collections by mapping and filtering iterables."
66
doc_path = "/tutorial/datastructures.html#list-comprehensions"
77
+++
88

9-
Comprehensions are expression forms for building collections from iterables. Read them from left to right: produce this value, for each item, optionally only when a condition is true.
9+
Comprehensions are expression forms for building concrete collections from iterables. Read them from left to right: produce this value, for each item, optionally only when a condition is true.
1010

1111
They are best for direct transformations where the expression is still easy to scan. When the work needs several statements or names, an explicit loop is usually clearer.
1212

13-
Comprehensions can build lists, dictionaries, and sets. Prefer them when the output shape is obvious from the expression.
13+
List, dictionary, and set comprehensions are eager: they build collections immediately. Generator expressions use similar syntax to stream values later and are covered in the Iteration section.
1414

1515
:::program
1616
```python
@@ -71,5 +71,6 @@ print(unique_scores)
7171
:::note
7272
- The left side says what to produce; the `for` clause says where values come from.
7373
- Use an `if` clause for simple filters.
74+
- List, dict, and set comprehensions build concrete collections immediately.
7475
- Switch to a loop when the transformation needs multiple steps or explanations.
7576
:::
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
+++
2+
slug = "generator-expressions"
3+
title = "Generator Expressions"
4+
section = "Iteration"
5+
summary = "Generator expressions use comprehension-like syntax to stream values lazily."
6+
doc_path = "/tutorial/classes.html#generator-expressions"
7+
+++
8+
9+
Generator expressions look like list comprehensions with parentheses, but they produce an iterator instead of building a concrete collection immediately.
10+
11+
Use them when a consumer such as `sum()`, `any()`, or a `for` loop can use values one at a time. This keeps the transformation close to the consumer and avoids storing intermediate lists.
12+
13+
Like other iterators, a generator expression is consumed as values are requested. Create a new generator expression when you need another pass.
14+
15+
:::program
16+
```python
17+
numbers = [1, 2, 3, 4]
18+
list_squares = [number * number for number in numbers]
19+
print(list_squares)
20+
21+
stream_squares = (number * number for number in numbers)
22+
print(next(stream_squares))
23+
print(next(stream_squares))
24+
print(list(stream_squares))
25+
26+
print(sum(number * number for number in numbers))
27+
```
28+
:::
29+
30+
:::cell
31+
A list comprehension is eager: it builds a list immediately. That is useful when you need to store or reuse the results.
32+
33+
```python
34+
numbers = [1, 2, 3, 4]
35+
list_squares = [number * number for number in numbers]
36+
print(list_squares)
37+
```
38+
39+
```output
40+
[1, 4, 9, 16]
41+
```
42+
:::
43+
44+
:::cell
45+
A generator expression is lazy: it creates an iterator that produces values as they are consumed. After two `next()` calls, only the remaining squares are left.
46+
47+
```python
48+
stream_squares = (number * number for number in numbers)
49+
print(next(stream_squares))
50+
print(next(stream_squares))
51+
print(list(stream_squares))
52+
```
53+
54+
```output
55+
1
56+
4
57+
[9, 16]
58+
```
59+
:::
60+
61+
:::cell
62+
Generator expressions are common inside reducing functions. When a generator expression is the only argument, the extra parentheses can be omitted.
63+
64+
```python
65+
print(sum(number * number for number in numbers))
66+
```
67+
68+
```output
69+
30
70+
```
71+
:::
72+
73+
:::note
74+
- List, dict, and set comprehensions build concrete collections.
75+
- Generator expressions produce one-pass iterators.
76+
- Use generator expressions when the consumer can process values one at a time.
77+
:::

src/example_sources/generators.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
slug = "generators"
33
title = "Generators"
44
section = "Iteration"
5-
summary = "yield produces a lazy sequence of values."
5+
summary = "yield creates an iterator that produces values on demand."
66
doc_path = "/tutorial/classes.html#generators"
77
+++
88

9-
A generator function uses `yield` to produce values lazily. Calling the function returns an iterator; the body runs only as values are requested.
9+
A generator function is a convenient way to write your own iterator. `yield` produces one value, pauses the function, and resumes when the next value is requested.
1010

1111
Generators are useful for pipelines, large inputs, and infinite sequences because they avoid building an entire collection in memory.
1212

@@ -64,7 +64,7 @@ for value in countdown(3):
6464
:::
6565

6666
:::note
67-
- Generator functions return iterators.
67+
- Generator functions are a concise way to create custom iterators.
6868
- Values are produced on demand.
6969
- A generator is consumed as you iterate over it.
7070
:::

src/example_sources/iterating-over-iterables.md

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,40 @@
22
slug = "iterating-over-iterables"
33
title = "Iterating over Iterables"
44
section = "Iteration"
5-
summary = "for loops work with any iterable, not just numeric ranges."
5+
summary = "for loops consume values from any iterable object."
66
doc_path = "/tutorial/controlflow.html#for-statements"
77
+++
88

9-
The for statement works with any iterable object: lists, strings, dictionaries, generators, files, and many standard-library helpers. This makes iteration a central Python protocol rather than a special case for arrays.
9+
Python's `for` statement consumes values from any iterable object: lists, strings, dictionaries, ranges, generators, files, and many standard-library helpers.
1010

11-
Use enumerate() when you need positions and values together, and dict.items() when you need keys and values. These helpers express intent better than manual indexing.
11+
This makes iteration a value-stream protocol rather than a special case for arrays. The producer decides how values are made, and the loop consumes them one at a time.
1212

13-
Python's for statement consumes iterables through the iterator protocol. Prefer enumerate() over range(len(...)) when you need an index.
13+
Use `enumerate()` when you need positions and values together, and `dict.items()` when you need keys and values. These helpers express intent better than manual indexing.
1414

1515
:::program
1616
```python
1717
names = ["Ada", "Grace", "Guido"]
1818

19-
# Iterate over values directly.
2019
for name in names:
2120
print(name)
2221

23-
# enumerate adds a counter without manual indexing.
2422
for index, name in enumerate(names):
2523
print(index, name)
2624

2725
scores = {"Ada": 10, "Grace": 9}
28-
29-
# items yields key/value pairs from a dictionary.
3026
for name, score in scores.items():
3127
print(name, score)
3228
```
3329
:::
3430

3531
:::cell
36-
Start with an ordinary list. A list is iterable, so a for loop can ask it for one value at a time.
32+
Start with an ordinary list. A list stores values, and a `for` loop asks it for one value at a time.
3733

3834
When you only need the values, iterate over the collection directly. There is no index variable because the loop body does not need one.
3935

4036
```python
4137
names = ["Ada", "Grace", "Guido"]
4238

43-
# Iterate over values directly.
4439
for name in names:
4540
print(name)
4641
```
@@ -53,10 +48,9 @@ Guido
5348
:::
5449

5550
:::cell
56-
When you need both a position and a value, use enumerate(). It keeps the counter tied to iteration without manual indexing.
51+
When you need both a position and a value, use `enumerate()`. It produces index/value pairs without manual indexing.
5752

5853
```python
59-
# enumerate adds a counter without manual indexing.
6054
for index, name in enumerate(names):
6155
print(index, name)
6256
```
@@ -69,12 +63,10 @@ for index, name in enumerate(names):
6963
:::
7064

7165
:::cell
72-
Dictionaries are iterable too, but dict.items() is the clearest way to say that the loop needs keys and values together.
66+
Dictionaries are iterable too, but `dict.items()` is the clearest way to say that the loop needs keys and values together.
7367

7468
```python
7569
scores = {"Ada": 10, "Grace": 9}
76-
77-
# items yields key/value pairs from a dictionary.
7870
for name, score in scores.items():
7971
print(name, score)
8072
```
@@ -86,6 +78,7 @@ Grace 9
8678
:::
8779

8880
:::note
89-
- Python's for statement consumes iterables through the iterator protocol.
90-
- Prefer enumerate() over range(len(...)) when you need an index.
81+
- A `for` loop consumes values from an iterable.
82+
- Different producers can feed the same loop protocol.
83+
- Prefer `enumerate()` over `range(len(...))` when you need an index.
9184
:::

src/example_sources/iterators.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
+++
2+
slug = "iterators"
3+
title = "Iterators"
4+
section = "Iteration"
5+
summary = "iter and next expose the protocol behind for loops."
6+
doc_path = "/library/stdtypes.html#iterator-types"
7+
+++
8+
9+
An iterable is an object that can produce values for a loop. An iterator is the object that remembers where that production currently is.
10+
11+
`iter()` asks an iterable for an iterator, and `next()` consumes one value from that iterator. A `for` loop performs those steps for you until the iterator is exhausted.
12+
13+
This is the core value-stream protocol in Python: one object produces values, another piece of code consumes them, and many streams are one-pass.
14+
15+
:::program
16+
```python
17+
names = ["Ada", "Grace", "Guido"]
18+
iterator = iter(names)
19+
print(next(iterator))
20+
print(next(iterator))
21+
22+
for name in iterator:
23+
print(name)
24+
25+
again = iter(names)
26+
print(next(again))
27+
```
28+
:::
29+
30+
:::cell
31+
`iter()` asks an iterable for an iterator. `next()` consumes one value and advances the iterator's position.
32+
33+
```python
34+
names = ["Ada", "Grace", "Guido"]
35+
iterator = iter(names)
36+
print(next(iterator))
37+
print(next(iterator))
38+
```
39+
40+
```output
41+
Ada
42+
Grace
43+
```
44+
:::
45+
46+
:::cell
47+
A `for` loop consumes the same iterator protocol. Because two values were already consumed, the loop sees only the remaining value.
48+
49+
```python
50+
for name in iterator:
51+
print(name)
52+
```
53+
54+
```output
55+
Guido
56+
```
57+
:::
58+
59+
:::cell
60+
The list itself is reusable. Asking it for a fresh iterator starts a new pass over the same stored values.
61+
62+
```python
63+
again = iter(names)
64+
print(next(again))
65+
```
66+
67+
```output
68+
Ada
69+
```
70+
:::
71+
72+
:::note
73+
- Iterables produce iterators; iterators produce values.
74+
- `next()` consumes one value from an iterator.
75+
- Many iterators are one-pass even when the original collection is reusable.
76+
:::

src/example_sources/itertools.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
slug = "itertools"
33
title = "Itertools"
44
section = "Iteration"
5-
summary = "itertools provides lazy iterator building blocks."
5+
summary = "itertools composes lazy iterator streams."
66
doc_path = "/library/itertools.html"
77
+++
88

9-
The `itertools` module contains iterator building blocks for combining, slicing, grouping, and repeating streams of values. These tools pair naturally with `for` loops and generators.
9+
The `itertools` module contains tools for composing iterator streams: combining, slicing, grouping, and repeating values without changing the consumer protocol.
1010

1111
Many `itertools` functions are lazy. They describe work to do later instead of building a list immediately, so helpers such as `islice()` are useful when taking a finite window.
1212

13-
Iterator pipelines let each step stay small. Convert to `list()` only at the edge when you need to display, store, or reuse the results.
13+
Iterator pipelines let each step stay small: one object produces values, another transforms them, and a final consumer such as `list()` or a loop pulls values through the pipeline.
1414

1515
:::program
1616
```python
@@ -71,6 +71,7 @@ print(list(high_scores))
7171
:::
7272

7373
:::note
74+
- `itertools` composes producer and transformer streams.
7475
- Iterator pipelines avoid building intermediate lists.
7576
- Use `islice()` to take a finite piece from an infinite iterator.
7677
- Convert to a list only when you need concrete results.

0 commit comments

Comments
 (0)