目录 | 上一节 (6.1 迭代协议) | 下一节 (6.3 生产者/消费者)

6.2 自定义迭代

本节探究如何使用生成器函数自定义迭代。

问题

假设你想要自定义迭代模式。

例如:倒数:

>>> for x in countdown(10):
... print(x, end=' ')
...
10 9 8 7 6 5 4 3 2 1
>>>

有一个j简单方法可以做到这一点。

生成器

生成器(generator)是定义了迭代的函数:

def countdown(n):
while n > 0:
yield n
n -= 1

示例:

>>> for x in countdown(10):
... print(x, end=' ')
...
10 9 8 7 6 5 4 3 2 1
>>>

任何使用了 yield 语句的函数称为生成器。

生成器函数的行为不同于普通于普通函数。调用生成器函数会创建一个生成器对象(generator object),而不是立即执行函数:

def countdown(n):
# Added a print statement
print('Counting down from', n)
while n > 0:
yield n
n -= 1
>>> x = countdown(10)
# There is NO PRINT STATEMENT
>>> x
# x is a generator object
<generator object at 0x58490>
>>>

生成器函数只在 __next__() 方法被调用时才执行:

>>> x = countdown(10)
>>> x
<generator object at 0x58490>
>>> x.__next__()
Counting down from 10
10
>>>

yield 生成一个值,但是挂起(suspend)函数执行。生成器函数会在下次调用 __next__() 方法时恢复(resume),

>>> x.__next__()
9
>>> x.__next__()
8

当生成器返回最后一个值后,再次迭代将会触发一个错误(译注:StopIteration)。

>>> x.__next__()
1
>>> x.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in ? StopIteration
>>>

观察:生成器函数实现的协议与 for 语句在列表、元组、字典、文件上使用的底层协议相同。

练习

练习 6.4:一个简单的生成器

如果想要自定义迭代,那么你应该始终考虑生成器函数。生成器函数非常容易编写——创建一个函数,执行所需的迭代逻辑,并使用 yield 发送一个值。

例如,创建一个在文件各行中查找匹配子串的生成器:

>>> def filematch(filename, substr):
with open(filename, 'r') as f:
for line in f:
if substr in line:
yield line >>> for line in open('Data/portfolio.csv'):
print(line, end='') name,shares,price
"AA",100,32.20
"IBM",50,91.10
"CAT",150,83.44
"MSFT",200,51.23
"GE",95,40.37
"MSFT",50,65.10
"IBM",100,70.44
>>> for line in filematch('Data/portfolio.csv', 'IBM'):
print(line, end='') "IBM",50,91.10
"IBM",100,70.44
>>>

这是一种有趣的思想——你可以在函数中隐藏自定义的处理过程,并将该函数应用于 for 循环。下一个例子探究一种更不寻常的情况。

练习 6.5:监视流数据源

生成器可应用于监视实时数据源(如:日志文件,股票市场消息)。本部分,我们将对“使用生成器监视实时数据源”这一思想进行探索。首先,请严格遵循以下说明。

Data/stocksim.py 用来模仿股市数据,将实时数据不断地写入到 Data/stocklog.csv 文件。请打开一个独立的命令行窗口,进入到 Data/ 目录,然后运行 stocksim.py 程序:

bash % python3 stocksim.py

如果你使用的是 Windows 系统,那么请找到 stocksim.py 文件,然后双击该文件运行。然后,让我们先把这个程序放到一边(让它一直在那运行),打开另外一个命令行窗口,查看正在被模拟程序(译注:stocksim.py)写入数据的 Data/stocklog.csv 文件(译注:如果使用的是 Linux 系统,那么可以进入到 Data 目录下,然后使用 tail -f stocklog.csv 命令查看)。你应该可以看到每隔几秒新的文本行被添加到 Data/stocklog.csv 文件中。同样,让程序在后台运行——该程序会运行几个小时(对此不用担心)。

stocksim.py 程序运行后,让我们编写一个程序来打开 Data/stocklog.csv 文件、移动到文件末尾、并查看新的输出。请在 Work 目录下创建 follow.py 文件,并把以下代码放入其中:

# follow.py
import os
import time f = open('Data/stocklog.csv')
f.seek(0, os.SEEK_END) # Move file pointer 0 bytes from end of file while True:
line = f.readline()
if line == '':
time.sleep(0.1) # Sleep briefly and retry
continue
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}')

运行 follow.py 程序,你将会看到实时的股票报价(stock ticker)。 follow.py 里的代码类似于 Unix 系统查看日志文件的 tail -f 命令。

注意事项:在本示例中,readline() 方法的使用与通常从文件中读取行的方式稍微有点不同(通常使用 for 循环)。在这种情况下,我们使用 readline() 来重复探测文件的末尾,以查看是否添加了新的数据(readline() 方法返回新的数据或者空字符串)。

练习 6.6:使用生成器生成数据

查看练习 6.5 中代码你会发现,代码的第一部分产生了几行数据,而 while 循环末尾的语句消费数据。生成器的一个主要特性是你可以将生成数据的代码移动到可重用的函数中。

请修改练习 6.5 的代码,以便通过生成器函数 follow(filename) 执行文件读取。请实现更改以便下面的代码能够工作:

>>> for line in follow('Data/stocklog.csv'):
print(line, end='') ... Should see lines of output produced here ...

请修改股票报价代码,使代码看起来像下面这样:

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}')

练习 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 运算符。请参阅 练习 6.3 ,确保 Portfolio 类实现了 __contains__() 运算符。

讨论

在这里,你将一个有趣的迭代模式(在文件末尾读取行)移动到函数中。follow()函数现在是可以在任何程序中使用的完全通用的实用程序。例如,你可以使用 follow() 函数查看服务器日志、调试日志、其它类似的数据源。

目录 | 上一节 (6.1 迭代协议) | 下一节 (6.3 生产者/消费者)

注:完整翻译见 https://github.com/codists/practical-python-zh

翻译:《实用的Python编程》06_02_Customizing_iteration的更多相关文章

  1. 翻译:《实用的Python编程》InstructorNotes

    实用的 Python 编程--讲师说明 作者:戴维·比兹利(David Beazley) 概述 对于如何使用我的课程"实用的 Python 编程"进行教学的问题,本文档提供一些通用 ...

  2. 翻译:《实用的Python编程》README

    欢迎光临 大约 25 年前,当我第一次学习 Python 时,发现 Python 竟然可以被高效地应用到各种混乱的工作项目上,我立即被震惊了.15 年前,我自己也将这种乐趣教授给别人.教学的结果就是本 ...

  3. 翻译:《实用的Python编程》05_02_Classes_encapsulation

    目录 | 上一节 (5.1 再谈字典) | 下一节 (6 生成器) 5.2 类和封装 创建类时,通常会尝试将类的内部细节进行封装.本节介绍 Python 编程中有关封装的习惯用法(包括私有变量和私有属 ...

  4. 翻译:《实用的Python编程》04_02_Inheritance

    目录 | 上一节 (4.1 类) | 下一节 (4.3 特殊方法) 4.2 继承 继承(inheritance)是编写可扩展程序程序的常用手段.本节对继承的思想(idea)进行探讨. 简介 继承用于特 ...

  5. 翻译:《实用的Python编程》01_02_Hello_world

    目录 | 上一节 (1.1 Python) | 下一节 (1.3 数字) 1.2 第一个程序 本节讨论有关如何创建一个程序.运行解释器和调试的基础知识. 运行 Python Python 程序始终在解 ...

  6. 翻译:《实用的Python编程》03_03_Error_checking

    目录 | 上一节 (3.2 深入函数) | 下一节 (3.4 模块) 3.3 错误检查 虽然前面已经介绍了异常,但本节补充一些有关错误检查和异常处理的其它细节. 程序是如何运行失败的 Python 不 ...

  7. 翻译:《实用的Python编程》03_04_Modules

    目录 | 上一节 (3.3 错误检查) | 下一节 (3.5 主模块) 3.4 模块 本节介绍模块的概念以及如何使用跨多个文件的函数. 模块和导入 任何一个 Python 源文件都是一个模块. # f ...

  8. 翻译:《实用的Python编程》03_05_Main_module

    目录 | 上一节 (3.4 模块) | 下一节 (3.6 设计讨论) 3.5 主模块 本节介绍主程序(主模块)的概念 主函数 在许多编程语言中,存在一个主函数或者主方法的概念. // c / c++ ...

  9. 翻译:《实用的Python编程》04_01_Class

    目录 | 上一节 (3.6 设计讨论) | 下一节 (4.2 继承) 4.1 类 本节介绍 class 语句以及创建新对象的方式. 面向对象编程(OOP) 面向对象编程是一种将代码组织成对象集合的编程 ...

随机推荐

  1. Bootstrap 中的 aria-label 和 aria-labelledby

    正常情况下,form表单的input组件都有对应的label.当input组件获取到焦点时,屏幕阅读器会读出相应的label里的文本. <form> <div class=" ...

  2. mysql(一)--mysql架构和执行流程

    1. 一条查询 SQL 语句是如何执行的? 我们的程序或者工具要操作数据库,第一步要做什么事情? 跟数据库建立连接.   1.1. 通信协议 首先,MySQL 必须要运行一个服务,监听默认的 3306 ...

  3. ORM框架对分表分库之分库和分表指定不同的字段

    ORM框架分库分表已实现了 只分表(根据指定字段) 点我查看demo 只分库(根据指定字段) 点我查看demo 既分库又分表(根据相同的字段) 点我查看demo 上面几点之前我在博客中已经写了使用介绍 ...

  4. input composition event All In One

    input composition event All In One input event compositionStart & compositionEnd & compositi ...

  5. node cli & emoji

    node cli & emoji cli $ yarn add node-emoji $ npm i node-emoji https://github.com/omnidan/node-em ...

  6. nasm astrcat_s函数 x86

    xxx.asm %define p1 ebp+8 %define p2 ebp+12 %define p3 ebp+16 section .text global dllmain export ast ...

  7. C++算法代码——标题统计

    题目来自:http://218.5.5.242:9018/JudgeOnline/problem.php?id=2327 题目描述 凯凯刚写了一篇美妙的作文,请问这篇作文的标题中有多少个字符? 注意: ...

  8. Scrapy 项目:腾讯招聘

    目的: 通过爬取腾讯招聘网站(https://careers.tencent.com/search.html)练习Scrapy框架的使用 步骤: 1.通过抓包确认要抓取的内容是否在当前url地址中,测 ...

  9. 一条sql语句的执行过程

    一条select语句执行流程 第一步:连接器 连接器负责跟客户端建立连接.获取权限.维持和管理连接.如果用户名密码验证通过后,连接器会到权限表里面查出你拥有的权限.之后该连接的权限验证都依赖于刚查出来 ...

  10. hadoop支持lzo完整过程

    简介 启用lzo 启用lzo的压缩方式对于小规模集群是很有用处,压缩比率大概能降到原始日志大小的1/3.同时解压缩的速度也比较快. 安装lzo lzo并不是linux系统原生支持,所以需要下载安装软件 ...