目录 | 上一节 (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. <U+200B> for, Zero Width Space ❌

    <U+200B> for, Zero Width Space zsh, bash https://www.cnblogs.com/xgqfrms/p/14233264.html#47944 ...

  2. Git常用命令速查表 & Git Basics & github : release 发布!

    Git常用命令速查表 & Git Basics  & github : release  发布! Git常用命令速查表: 1 1 1 1 1 http://git-scm.com/bo ...

  3. soft tab

    soft tab hard-tabs 是硬件 tab,就是按一个 tab 键; soft-tabs 是软件 tab,通过按 4个 space 键实现; refs Tabs vs. Spaces, FR ...

  4. js 快速排序 All In One

    js 快速排序 All In One 快速排序 / Quick Sort "use strict"; /** * * @author xgqfrms * @license MIT ...

  5. Flutter: SearchDelegate 委托showSearch定义搜索页面的内容

    API class _MyHomeState extends State<MyHome> { List<String> _list = List.generate(100, ( ...

  6. 加州金融专访NGK,就NGK DeFi+展开讨论

    近日,加利福尼亚金融日报联合数家知名媒体就DeFi+行业专访了NGK团队代表特德惠斯基. 首先,加利福尼亚金融日报专栏记者迈尔斯表示,目前区块链领域,去中心化金融(DeFi+)的概念是目前市场上面最火 ...

  7. [Python学习笔记]正则表达式总结

    常用缩写字符及其含义表格查询 缩写字符分类 含义 \d 0-9的任意数字 \D 除0-9的数字以外的任何字符 \w 任何字母.数字或下划线字符(可以认为是匹配"单词"字符) \W ...

  8. (十) 数据库查询处理之排序(sorting)

    1. 为什么我们需要对数据排序 可以支持对于重复元素的清除(支持DISTINCT) 可以支持GROUP BY 操作 对于关系运算中的一些运算能够得到高效的实现 2. 引入外部排序算法 对于不能全部放在 ...

  9. 不可不知的 JVM 预热

    一.JVM 架构基础 JVM 进程启动时,ClassLoader 会将需要的所有类加载到内存,主要分为以下三步: Bootstrap Class: 核心类库,由 "Bootstrap Cla ...

  10. 深入剖析 ConcurrentHashMap

    自建博客地址:https://bytelife.net,欢迎访问! 本文为博客自动同步文章,为了更好的阅读体验,建议您移步至我的博客 本文作者: Jeffrey 本文链接: https://bytel ...