配套专栏:Python 全栈修炼之路 第 13 篇《迭代器与生成器——惰性求值的艺术》
难度分布:⭐ → ⭐⭐ → ⭐⭐ → ⭐⭐⭐ → ⭐⭐⭐ → ⭐⭐⭐⭐
核心覆盖:
__iter__/__next__、yield、生成器表达式、itertools、yield from、协程通信
前言
第十三篇是进阶修炼篇的开篇,核心主题是惰性求值——用最少的内存处理最多的数据。本练习精选 6 道编程题,从自定义迭代器到协程管道,层层递进,帮你彻底掌握迭代器与生成器的精髓。
题目一:自定义迭代器——倒计时与步进器 ⭐
📌 题目描述
实现两个自定义迭代器类,手动实现 __iter__ 和 __next__ 协议:
# 倒计时迭代器
cd = CountDown(5)
print(list(cd)) # [5, 4, 3, 2, 1]
# 步进迭代器(支持任意步长和方向)
step = Stepper(0, 10, 2)
print(list(step)) # [0, 2, 4, 6, 8, 10]
step2 = Stepper(10, 0, -3)
print(list(step2)) # [10, 7, 4, 1]
# 迭代器只能遍历一次
cd2 = CountDown(3)
print(list(cd2)) # [3, 2, 1]
print(list(cd2)) # [](已耗尽)
💡 编程思路
这道题考察 迭代器协议:__iter__ 返回 self,__next__ 产生下一个值或抛出 StopIteration。
CountDown:内部维护一个计数器self.current,每次__next__返回当前值并递减,到 0 时抛出StopIteration。Stepper:支持start、stop、step三个参数,类似range()但作为类实现。需要处理正步长和负步长两种情况。- 一次性特性:迭代器耗尽后再次遍历返回空列表,这是迭代器与可迭代对象的核心区别。
🖥️ 参考代码
from collections.abc import Iterator
class CountDown:
"""倒计时迭代器:从 start 倒数到 1。"""
def __init__(self, start: int):
if start < 0:
raise ValueError("start 必须为非负整数")
self.current = start
def __iter__(self) -> "CountDown":
return self
def __next__(self) -> int:
if self.current <= 0:
raise StopIteration
value = self.current
self.current -= 1
return value
def __repr__(self) -> str:
return f"CountDown({
self.current})"
class Stepper:
"""步进迭代器:类似 range(),但以迭代器协议实现。"""
def __init__(self, start: int, stop: int, step: int = 1):
if step == 0:
raise ValueError("step 不能为 0")
self.start = start
self.stop = stop
self.step = step
self.current = start
self._exhausted = False
def __iter__(self) -> "Stepper":
return self
def __next__(self) -> int:
if self._exhausted:
raise StopIteration
if self.step > 0 and self.current > self.stop:
self._exhausted = True
raise StopIteration
if self.step < 0 and self.current < self.stop:
self._exhausted = True
raise StopIteration
value = self.current
self.current += self.step
return value
def __repr__(self) -> str:
return f"Stepper({
self.start}, {
self.stop}, {
self.step})"
class Repeater:
"""重复元素迭代器:无限重复指定元素 n 次。"""
def __init__(self, value, times: int):
self.value = value
self.times = times
self.count = 0
def __iter__(self):
return self
def __next__(self):
if self.count >= self.times:
raise StopIteration
self.count += 1
return self.value
# 测试
if __name__ == "__main__":
print("=== CountDown ===")
cd = CountDown(5)
print(f"类型: {
type(cd)}")
print(f"是迭代器: {
isinstance(cd, Iterator)}")
print(f"遍历: {
list(cd)}")
print(f"再次遍历: {
list(cd)} # 已耗尽")
print("\n=== Stepper ===")
print(f"正步长: {
list(Stepper(0, 10, 2))}")
print(f"负步长: {
list(Stepper(10, 0, -3))}")
print(f"单元素: {
list(Stepper(5, 5, 1))}")
print("\n=== Repeater ===")
print(f"重复 5 次: {
list(Repeater('Hello', 5))}")
print("\n=== for 循环兼容 ===")
for i in CountDown(3):
print(f" 倒计时: {
i}")
print("\n所有测试通过 ✓")
🔗 关联知识点
| 知识点 | 说明 |
|---|---|
__iter__ |
返回迭代器自身 |
__next__ |
返回下一个值或 StopIteration |
| 迭代器一次性 | 耗尽后不可复用 |
isinstance(obj, Iterator) |
迭代器类型检查 |
for 循环原理 |
自动调用 iter() 和 next() |
题目二:生成器函数——斐波那契与素数 ⭐⭐
📌 题目描述
使用 yield 实现两个经典数学序列生成器:
# 斐波那契数列(前 N 个)
fibs = list(fibonacci(10))
print(fibs) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
# 无限斐波那契 + islice 截取
from itertools import islice
print(list(islice(fibonacci_infinite(), 8))) # [0, 1, 1, 2, 3, 5, 8, 13]
# 素数生成器(前 N 个)
primes = list(prime_generator(10))
print(primes) # [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
# 无限素数 + takewhile 截取
from itertools import takewhile
print(list(takewhile(lambda x: x < 50, prime_infinite()))) # [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47]
# 内存对比
import sys
fib_list = [fibonacci(100000)] # 大量内存
fib_gen = fibonacci(100000) # 几乎不占内存
print(f"列表: {
sys.getsizeof(list(fibonacci(1000)))} bytes")
print(f"生成器: {
sys.getsizeof(fibonacci(1000))} bytes")
💡 编程思路
这道题考察 yield 惰性求值 + 无限序列 + itertools 截取:
- 有限斐波那契:
yield每次产生一个数,用count控制数量。 - 无限斐波那契:
while True+yield,永不停止,配合islice截取。 - 素数生成器:维护一个素数列表,用试除法判断新数是否为素数。
- 内存优势:生成器不存储所有值,只在需要时计算,适合大数据量。
🖥️ 参考代码
import sys
import time
from itertools import islice, takewhile
def fibonacci(n: int):
"""生成前 n 个斐波那契数(有限生成器)。"""
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b
def fibonacci_infinite():
"""无限斐波那契数列生成器。"""
a, b = 0, 1
while True:
yield a
a, b = b, a + b
def prime_generator(n: int):
"""生成前 n 个素数。"""
primes = []
candidate = 2
while len(primes) < n:
is_prime = True
for p in primes:
if p * p > candidate:
break
if candidate % p == 0:
is_prime = False
break
if is_prime:
primes.append(candidate)
yield candidate
candidate += 1
def prime_infinite():
"""无限素数生成器。"""
primes = []
candidate = 2
while True:
is_prime = True
for p in primes:
if p * p > candidate:
break
if candidate % p == 0:
is_prime = False
break
if is_prime:
primes.append(candidate)
yield candidate
candidate += 1
def collatz(start: int):
"""考拉兹猜想(冰雹猜想)序列生成器。"""
n = start
while n != 1:
yield n
if n % 2 == 0:
n = n // 2
else:
n = 3 * n + 1
yield 1
# 测试
if __name__ == "__main__":
print("=== 斐波那契(有限) ===")
print(f"前 10 个: {
list(fibonacci(10))}")
print(f"前 20 个: {
list(fibonacci(20))}")
print("\n=== 斐波那契(无限 + islice) ===")
print(f"第 5~15 个: {
list(islice(fibonacci_infinite(), 5, 15))}")
print("\n=== 素数生成器 ===")
print(f"前 10 个素数: {
list(prime_generator(10))}")
print(f"小于 50 的素数: {
list(takewhile(lambda x: x < 50, prime_infinite()))}")
print("\n=== 考拉兹序列 ===")
print(f"start=27: {
list(collatz(27))}")
print(f"start=6: {
list(collatz(6))}")
print("\n=== 内存对比 ===")
n = 1000
fib_list = list(fibonacci(n))
fib_gen = fibonacci(n)
print(f"列表内存: {
sys.getsizeof(fib_list):,} bytes")
print(f"生成器内存: {
sys.getsizeof(fib_gen)} bytes")
print(f"内存比: {
sys.getsizeof(fib_list) / sys.getsizeof(fib_gen):.0f}x"


18万+

被折叠的 条评论
为什么被折叠?



