5.8 KiB
doc_type, full_text
| doc_type | full_text |
|---|---|
| short | sources/02_Customizing_iteration.md |
02_Customizing_iteration 总结
本文介绍如何用生成器函数自定义 Python 的迭代行为。核心思想是:如果想定义一种新的迭代模式,应优先考虑使用带有 yield 的生成器函数,因为它能把数据生产逻辑封装成可被 for 循环直接消费的对象。
核心概念
生成器用于定义迭代
生成器是包含 yield 语句的函数,用来定义自定义迭代逻辑。例如倒计时:
def countdown(n):
while n > 0:
yield n
n -= 1
调用 countdown(10) 不会立即执行函数体,而是返回一个生成器对象。生成器对象实现了 Python 迭代协议,因此可以被 for 循环使用,也可以手动调用 __next__() 获取下一个值。
相关主题:生成器、迭代协议、yield。
yield 的执行模型
yield 会产生一个值,同时暂停函数执行。下一次调用 __next__() 时,函数会从暂停处继续运行。
当生成器函数执行结束时,会触发 StopIteration,这与列表、元组、字典、文件等对象在 for 循环中遵循的底层迭代机制一致。
示例:文件内容匹配生成器
文档通过 filematch(filename, substr) 展示了如何把“逐行搜索文件并返回匹配行”的逻辑封装为生成器:
def filematch(filename, substr):
with open(filename, 'r') as f:
for line in f:
if substr in line:
yield line
这样,复杂的数据筛选逻辑可以隐藏在函数内部,而外部仍然可以用自然的 for 循环消费结果:
for line in filematch('Data/portfolio.csv', 'IBM'):
print(line, end='')
这个例子体现了生成器的一个重要价值:把自定义数据生产过程变成可复用的迭代器。
相关主题:文件处理、惰性求值、数据过滤。
示例:监控流式数据源
文档进一步展示了生成器在实时数据源中的应用。Data/stocksim.py 会持续向 Data/stocklog.csv 写入模拟股票行情数据。程序可以打开该文件,移动到文件末尾,然后不断调用 readline() 检查是否有新数据追加。
基本逻辑类似 Unix 的 tail -f:
f = open('Data/stocklog.csv')
f.seek(0, os.SEEK_END)
while True:
line = f.readline()
if line == '':
time.sleep(0.1)
continue
# 消费 line
这里的 readline() 用法不同于常规文件读取。它不是一次性遍历已有内容,而是反复探测文件末尾是否出现了新行。
相关主题:流式数据、日志监控、tail f模式。
将数据生产逻辑封装为 follow() 生成器
Exercise 6.6 的重点是把文件跟踪逻辑抽取为通用生成器函数 follow(filename)。这样,文件读取和数据消费可以分离:
def follow(filename):
f = open(filename)
f.seek(0, os.SEEK_END)
while True:
line = f.readline()
if line == '':
time.sleep(0.1)
continue
yield line
使用时可以写成:
for line in follow('Data/stocklog.csv'):
print(line, end='')
股票行情程序也可以改写为只负责消费数据:
if __name__ == '__main__':
for line in follow('Data/stocklog.csv'):
fields = line.split(',')
name = fields[0].strip('"')
price = float(fields[1])
change = float(fields[4])
if change < 0:
print(f'{name:>10s} {price:>10.2f} {change:>10.2f}')
这体现了生成器的设计优势:生产者逻辑和消费者逻辑可以解耦。相关主题:concepts/生产者消费者模式、生成器管道。
示例:只监控投资组合中的股票
Exercise 6.7 要求修改 follow.py,让程序只显示投资组合中已有股票的行情:
if __name__ == '__main__':
import report
portfolio = report.read_portfolio('Data/portfolio.csv')
for line in follow('Data/stocklog.csv'):
fields = line.split(',')
name = fields[0].strip('"')
price = float(fields[1])
change = float(fields[4])
if name in portfolio:
print(f'{name:>10s} {price:>10.2f} {change:>10.2f}')
这里依赖 Portfolio 类支持 in 运算符,即实现 __contains__()。这与前一节 summaries/01_Iteration_protocol 中介绍的迭代协议和容器协议相关。
相关主题:容器协议、contains、投资组合数据模型。
主要收获
- 生成器函数是自定义迭代模式的首选工具。
- 调用生成器函数只会创建生成器对象,不会立即执行函数体。
yield会产出一个值并暂停函数,下一次迭代时继续执行。- 生成器对象遵循 Python 的迭代协议,可被
for循环自然消费。 - 生成器可以把复杂的数据生产逻辑封装为通用、可复用的函数。
follow()示例展示了生成器在实时日志、股票行情、服务器监控等流式场景中的应用。- 将生产者与消费者分离,是后续 concepts/生产者消费者模式 和生成器管道设计的基础。
与前后章节的联系
本文建立在 summaries/01_Iteration_protocol 对迭代协议的介绍之上,展示如何通过生成器函数实现同样的低层协议。它也为下一节 summaries/03_Producers_consumers 中更系统的生产者/消费者模型和数据处理管道奠定基础。