-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexamples.py
More file actions
113 lines (86 loc) · 4.81 KB
/
Copy pathexamples.py
File metadata and controls
113 lines (86 loc) · 4.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
"""
Chapter 17: Iterators, Generators, and Classic Coroutines
=========================================================
Original implementations exploring lazy evaluation, the 'yield'
suspension mechanic, generator delegation via 'yield from',
and classic coroutines using '.send()'.
"""
import sys
import re
sys.stdout.reconfigure(encoding="utf-8")
def section(title: str) -> None:
print(f"\n{'=' * 60}\n=== {title}\n{'=' * 60}")
# ─────────────────────────────────────────────────────────────────────────────
# 1. Lazy Generators (yield)
# ─────────────────────────────────────────────────────────────────────────────
# Instead of building a massive list in memory, we yield one item at a time.
# This makes processing massive datasets O(1) in memory.
WORD_RE = re.compile(r'\w+')
class LazySentence:
def __init__(self, text):
self.text = text
def __iter__(self):
"""This method is a generator function because it contains 'yield'."""
# Finditer evaluates lazily. It doesn't build a list of all matches.
for match in WORD_RE.finditer(self.text):
yield match.group()
def demo_lazy_generator() -> None:
section("Part 1: Lazy Generators")
s = LazySentence("Generators suspend their state and return control to the caller.")
# We iterate over the generator. It yields one word, pauses, and resumes.
for word in s:
print(f" Yielded: {word}")
# ─────────────────────────────────────────────────────────────────────────────
# 2. Generator Delegation (yield from)
# ─────────────────────────────────────────────────────────────────────────────
# 'yield from' links a caller directly to an inner generator, bypassing the
# outer generator loop entirely.
def sub_generator():
yield "Sub-Gen 1"
yield "Sub-Gen 2"
def delegating_generator():
yield "Outer start"
# Instead of `for x in sub_generator(): yield x`
yield from sub_generator()
yield "Outer end"
def demo_yield_from() -> None:
section("Part 2: 'yield from' Delegation")
for item in delegating_generator():
print(f" {item}")
# ─────────────────────────────────────────────────────────────────────────────
# 3. Classic Coroutines (.send())
# ─────────────────────────────────────────────────────────────────────────────
# A generator becomes a coroutine when you send data INTO it using .send()
# instead of just pulling data out of it using next().
def moving_average_coroutine():
"""A coroutine that computes a running average."""
total = 0.0
count = 0
average = None
while True:
# Execution SUSPENDS here. It waits for `.send(term)`
term = yield average
# If term is None, the coroutine can handle it or exit
if term is None:
break
total += term
count += 1
average = total / count
def demo_coroutine() -> None:
section("Part 3: Classic Coroutines (.send)")
coro = moving_average_coroutine()
# PRIMING: We MUST call next(coro) or coro.send(None) first to
# advance execution to the very first `yield` statement.
next(coro)
# Now we send data INTO the suspended generator
print(f" Sent 10, average: {coro.send(10)}")
print(f" Sent 20, average: {coro.send(20)}")
print(f" Sent 30, average: {coro.send(30)}")
coro.close() # Cleanly shuts down the coroutine
# ─────────────────────────────────────────────────────────────────────────────
# MAIN
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
demo_lazy_generator()
demo_yield_from()
demo_coroutine()