-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path16_generators_and_scope.py
More file actions
125 lines (98 loc) · 3.8 KB
/
Copy path16_generators_and_scope.py
File metadata and controls
125 lines (98 loc) · 3.8 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
114
115
116
117
118
119
120
121
122
123
124
125
"""
Module 16: Generators and Variable Scope
Learn: local vs global scope, yield, generator expressions
In the advanced courses, you'll see:
for chunk in response: # streaming LLM responses are generators
print(chunk.choices[0].delta.content, end="")
def process_documents(paths):
for path in paths:
yield load_and_chunk(path)
"""
# =============================================
# PART 1: Variable Scope
# =============================================
# --- Local vs global scope ---
model = "gpt-4o" # global variable
def switch_model():
model = "claude-3.5-sonnet" # this creates a LOCAL variable, doesn't change global
print(f"Inside function: {model}")
switch_model()
print(f"Outside function: {model}") # still "gpt-4o"
# --- The global keyword (use sparingly!) ---
request_count = 0
def track_request():
global request_count # now we're modifying the global variable
request_count += 1
track_request()
track_request()
print(f"Requests tracked: {request_count}") # 2
# A better pattern — use a mutable container instead of global
state = {"count": 0}
def track_request_v2():
state["count"] += 1 # modifying a dict's contents doesn't need global
track_request_v2()
track_request_v2()
print(f"Requests tracked (v2): {state['count']}") # 2
# --- Nested scope (closures) ---
def make_logger(prefix):
"""Returns a function that remembers the prefix."""
def log(message):
print(f"[{prefix}] {message}")
return log
api_log = make_logger("API")
db_log = make_logger("DB")
api_log("Request sent") # [API] Request sent
db_log("Query executed") # [DB] Query executed
# =============================================
# PART 2: Generators
# =============================================
# --- Generators with yield ---
# A generator produces values one at a time (lazy evaluation)
# Instead of building a whole list in memory, it yields items on demand
def count_up_to(n):
"""Like range(), but as a generator."""
i = 1
while i <= n:
yield i
i += 1
# Using the generator
for num in count_up_to(5):
print(num, end=" ")
print() # newline
# --- Why generators matter: memory efficiency ---
# List: builds everything in memory at once
big_list = [x * x for x in range(1000)] # 1000 items in memory
# Generator: produces one item at a time
big_gen = (x * x for x in range(1000)) # almost no memory used
print(f"Generator object: {big_gen}")
print(f"First value: {next(big_gen)}") # 0
print(f"Second value: {next(big_gen)}") # 1
# --- Simulating streaming LLM responses ---
def stream_response(text):
"""Simulates how LLM APIs stream tokens."""
words = text.split()
for word in words:
yield word + " "
print("\nStreaming: ", end="")
for token in stream_response("Python generators are perfect for streaming"):
print(token, end="", flush=True)
print() # newline
# --- Generator for processing large document collections ---
def process_documents(file_paths):
"""Process files one at a time — doesn't load all into memory."""
for path in file_paths:
yield f"Processed: {path}"
docs = ["report.pdf", "data.csv", "notes.txt", "manual.pdf"]
for result in process_documents(docs):
print(result)
# --- Generator expressions vs list comprehensions ---
# List comprehension: [expr for x in iterable] → builds full list
# Generator expression: (expr for x in iterable) → lazy, one at a time
# Sum of squares — generator version uses almost no memory
total = sum(x * x for x in range(100))
print(f"\nSum of squares (0-99): {total}")
# any() and all() work great with generator expressions
words = ["hello", "world", "python"]
has_long_word = any(len(w) > 4 for w in words)
print(f"Has word longer than 4 chars: {has_long_word}")
# 🎯 Exercise: Write a generator that yields Fibonacci numbers up to a limit