Python Iterators and Generators
# Python Iterators and Generators
Welcome to Chapter 20! Iterators and generators enable lazy evaluation — processing data one item at a time without loading everything into memory.
---
1. Learning Objectives
- Understand the iterator protocol.
- Create custom iterators.
-
Write generator functions with
yield.
- Use generator expressions.
- Understand lazy vs eager evaluation.
---
2. Iterators
```python id="py20_ex1" # Every iterable has an iterator numbers = [1, 2, 3] iterator = iter(numbers)
print(next(iterator)) # 1 print(next(iterator)) # 2 print(next(iterator)) # 3 # print(next(iterator)) # StopIteration error!
# for loops use iterators internally for num in [10, 20, 30]: # Python calls iter() and next() behind the scenes print(num)
python id="py20ex2" class Countdown: def init(self, start): self.start = start def iter(self): self.current = self.start return self def next_(self): if self.current <= 0: raise StopIteration val = self.current self.current -= 1 return val
for num in Countdown(5): print(num, end=" ") # 5 4 3 2 1
python id="py20_ex3" def countdown(n): while n > 0: yield n # Pauses here, returns value n -= 1 # Resumes here on next call
for num in countdown(5): print(num, end=" ") # 5 4 3 2 1
# Generator produces values lazily (one at a time) gen = countdown(3) print(next(gen)) # 3 print(next(gen)) # 2 print(next(gen)) # 1
python id="py20_ex4" def fibonacci(limit): a, b = 0, 1 while a < limit: yield a a, b = b, a + b
fibs = list(fibonacci(100)) print(fibs) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
python id="py20ex5" # List comprehension (eager — creates entire list) squareslist = [x2 for x in range(1000000)] # Uses lots of memory!
# Generator expression (lazy — one at a time) squaresgen = (x2 for x in range(1000000)) # Almost no memory!
print(sum(squaresgen)) # Works, but memory efficient
# Use generators when you don't need the whole list total = sum(x**2 for x in range(100)) # No parentheses needed inside function print(total)
python id="py20ex6" # return — ends function, sends one value def getsquareslist(n): result = [] for i in range(n): result.append(i2) return result # All at once
# yield — pauses function, sends values one at a time def getsquares_gen(n): for i in range(n): yield i2 # One at a time
return vs yield: return: [0, 1, 4, 9, 16] → All in memory at once yield: 0 → 1 → 4 → 9 → 16 → One at a time
python id="py20ex7" def readlargefile(filepath): """Memory-efficient file reader""" with open(filepath, "r") as f: for line in f: yield line.strip()
# Process millions of lines without loading all into memory
# for line in readlargefile("hugefile.txt"):
# process(line)
``
---
7. MCQs with Answers
Q1: yield makes a function a:
A) Class B) Generator C) Decorator D) Module
Answer: B
Q2: Generators are: A) Eager B) Lazy C) Parallel D) Recursive Answer: B — They produce values on demand.
Q3: next() on exhausted iterator raises:
A) ValueError B) IndexError C) StopIteration D) GeneratorExit
Answer: C
Q4: Generator expression syntax uses: A) [] B) {} C) () D) <> Answer: C — Parentheses instead of square brackets.
Q5: Iterator protocol requires:
A) _iter only B) next only C) Both iter and next D) call
Answer: C
Q6: Generators are memory efficient because: A) They compress data B) They produce values one at a time C) They use caching D) They run faster Answer: B
Q7: yield vs return:
A) Same B) yield pauses, return ends C) return pauses D) yield ends
Answer: B
Q8: Can a generator be iterated twice? A) Yes B) No — it's exhausted after one pass Answer: B
Q9: sum(x for x in range(10)) uses:
A) List B) Generator expression C) Tuple D) Set
Answer: B
Q10: send() on generator:
A) Sends value into generator B) Sends email C) Error D) Stops generator
Answer: A
---
8. Interview Questions
-
1.
Iterator vs Iterable? Iterable has iter()
(lists, strings). Iterator has bothiter()andnext().
- 2. Why use generators? Memory efficiency — they don't store all values at once.
-
3.
Can generators be infinite? Yes! while True: yield value
. Usenext()orislice()to consume.
-
4.
What is yield from
?Delegates to a sub-generator: yield from anothergenerator().
- 5. Generator vs list comprehension? Generators are lazy (memory efficient); list comprehensions create the full list.
---
9. Summary
-
Iterators implement _iter()
andnext_().
-
Generators use yield
to produce values lazily.
-
Generator expressions use ()
instead of[]`.
- Generators are memory efficient — perfect for large datasets.
- A generator can only be iterated once.
---
10. Next Chapter Recommendation
In Chapter 21: Python Decorators, you'll learn to modify function behavior without changing their code! 🚀