Pythonyield使用浅析
转自:https://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/
您可能听说过,带有 yield 的函数在 Python 中被称之为 generator(生成器),何谓 generator ?
我们先抛开 generator,以一个常见的编程题目来展示 yield 的概念。
如何生成斐波那契數列
斐波那契(Fibonacci)數列是一个非常简单的递归数列,除第一个和第二个数外,任意一个数都可由前两个数相加得到。用计算机程序输出斐波那契數列的前 N 个数是一个非常简单的问题,许多初学者都可以轻易写出如下函数:
清单 1. 简单输出斐波那契數列前 N 个数
1
2
3
4
5
6
|
def fab(max): n, a, b = 0, 0, 1 while n < max: print b a, b = b, a + b n = n + 1 |
执行 fab(5),我们可以得到如下输出:
1
2
3
4
5
6
|
>>> fab(5) 1 1 2 3 5 |
结果没有问题,但有经验的开发者会指出,直接在 fab 函数中用 print 打印数字会导致该函数可复用性较差,因为 fab 函数返回 None,其他函数无法获得该函数生成的数列。
要提高 fab 函数的可复用性,最好不要直接打印出数列,而是返回一个 List。以下是 fab 函数改写后的第二个版本:
清单 2. 输出斐波那契數列前 N 个数第二版
1
2
3
4
5
6
7
8
|
def fab(max): n, a, b = 0, 0, 1 L = [] while n < max: L.append(b) a, b = b, a + b n = n + 1 return L |
可以使用如下方式打印出 fab 函数返回的 List:
1
2
3
4
5
6
7
8
|
>>> for n in fab(5): ... print n ... 1 1 2 3 5 |
改写后的 fab 函数通过返回 List 能满足复用性的要求,但是更有经验的开发者会指出,该函数在运行中占用的内存会随着参数 max 的增大而增大,如果要控制内存占用,最好不要用 List
来保存中间结果,而是通过 iterable 对象来迭代。例如,在 Python2.x 中,代码:
清单 3. 通过 iterable 对象来迭代
1
|
for i in range(1000): pass |
会导致生成一个 1000 个元素的 List,而代码:
1
|
for i in xrange(1000): pass |
则不会生成一个 1000 个元素的 List,而是在每次迭代中返回下一个数值,内存空间占用很小。因为 xrange 不返回 List,而是返回一个 iterable 对象。
利用 iterable 我们可以把 fab 函数改写为一个支持 iterable 的 class,以下是第三个版本的 Fab:
清单 4. 第三个版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
class Fab(object): def __init__(self, max): self.max = max self.n, self.a, self.b = 0, 0, 1 def __iter__(self): return self def next(self): if self.n < self.max: r = self.b self.a, self.b = self.b, self.a + self.b self.n = self.n + 1 return r raise StopIteration() |
Fab 类通过 next() 不断返回数列的下一个数,内存占用始终为常数:
1
2
3
4
5
6
7
8
|
>>> for n in Fab(5): ... print n ... 1 1 2 3 5 |
然而,使用 class 改写的这个版本,代码远远没有第一版的 fab 函数来得简洁。如果我们想要保持第一版 fab 函数的简洁性,同时又要获得 iterable 的效果,yield 就派上用场了:
清单 5. 使用 yield 的第四版
1
2
3
4
5
6
7
8
9
|
def fab(max): n, a, b = 0, 0, 1 while n < max: yield b # print b a, b = b, a + b n = n + 1 ''' |
第四个版本的 fab 和第一版相比,仅仅把 print b 改为了 yield b,就在保持简洁性的同时获得了 iterable 的效果。
调用第四版的 fab 和第二版的 fab 完全一致:
1
2
3
4
5
6
7
8
|
>>> for n in fab(5): ... print n ... 1 1 2 3 5 |
简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 fab(5) 不会执行 fab 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。
也可以手动调用 fab(5) 的 next() 方法(因为 fab(5) 是一个 generator 对象,该对象具有 next() 方法),这样我们就可以更清楚地看到 fab 的执行流程:
清单 6. 执行流程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
>>> f = fab(5) >>> f.next() 1 >>> f.next() 1 >>> f.next() 2 >>> f.next() 3 >>> f.next() 5 >>> f.next() Traceback (most recent call last): File "< stdin >", line 1, in < module > StopIteration |
当函数执行结束时,generator 自动抛出 StopIteration 异常,表示迭代完成。在 for 循环里,无需处理 StopIteration 异常,循环会正常结束。
我们可以得出以下结论:
一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。
yield 的好处是显而易见的,把一个函数改写为一个 generator 就获得了迭代能力,比起用类的实例保存状态来计算下一个 next() 的值,不仅代码简洁,而且执行流程异常清晰。
如何判断一个函数是否是一个特殊的 generator 函数?可以利用 isgeneratorfunction 判断:
清单 7. 使用 isgeneratorfunction 判断
1
2
3
|
>>> from inspect import isgeneratorfunction >>> isgeneratorfunction(fab) True |
要注意区分 fab 和 fab(5),fab 是一个 generator function,而 fab(5) 是调用 fab 返回的一个 generator,好比类的定义和类的实例的区别:
清单 8. 类的定义和类的实例
1
2
3
4
5
|
>>> import types >>> isinstance(fab, types.GeneratorType) False >>> isinstance(fab(5), types.GeneratorType) True |
fab 是无法迭代的,而 fab(5) 是可迭代的:
1
2
3
4
5
|
>>> from collections import Iterable >>> isinstance(fab, Iterable) False >>> isinstance(fab(5), Iterable) True |
每次调用 fab 函数都会生成一个新的 generator 实例,各实例互不影响:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
>>> f1 = fab(3) >>> f2 = fab(5) >>> print 'f1:', f1.next() f1: 1 >>> print 'f2:', f2.next() f2: 1 >>> print 'f1:', f1.next() f1: 1 >>> print 'f2:', f2.next() f2: 1 >>> print 'f1:', f1.next() f1: 2 >>> print 'f2:', f2.next() f2: 2 >>> print 'f2:', f2.next() f2: 3 >>> print 'f2:', f2.next() f2: 5 |
return 的作用
在一个 generator function 中,如果没有 return,则默认执行至函数完毕,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。
另一个例子
另一个 yield 的例子来源于文件读取。如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取:
清单 9. 另一个 yield 的例子
1
2
3
4
5
6
7
8
9
|
def read_file(fpath): BLOCK_SIZE = 1024 with open(fpath, 'rb') as f: while True: block = f.read(BLOCK_SIZE) if block: yield block else: return |
以上仅仅简单介绍了 yield 的基本概念和用法,yield 在 Python 3 中还有更强大的用法,我们会在后续文章中讨论。
注:本文的代码均在 Python 2.7 中调试通过
Pythonyield使用浅析的更多相关文章
- SQL Server on Linux 理由浅析
SQL Server on Linux 理由浅析 今天的爆炸性新闻<SQL Server on Linux>基本上在各大科技媒体上刷屏了 大家看到这个新闻都觉得非常震精,而美股,今天微软开 ...
- 【深入浅出jQuery】源码浅析--整体架构
最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...
- 高性能IO模型浅析
高性能IO模型浅析 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞IO(Non-blocking ...
- netty5 HTTP协议栈浅析与实践
一.说在前面的话 前段时间,工作上需要做一个针对视频质量的统计分析系统,各端(PC端.移动端和 WEB端)将视频质量数据放在一个 HTTP 请求中上报到服务器,服务器对数据进行解析.分拣后从不同的 ...
- Jvm 内存浅析 及 GC个人学习总结
从诞生至今,20多年过去,Java至今仍是使用最为广泛的语言.这仰赖于Java提供的各种技术和特性,让开发人员能优雅的编写高效的程序.今天我们就来说说Java的一项基本但非常重要的技术内存管理 了解C ...
- 从源码浅析MVC的MvcRouteHandler、MvcHandler和MvcHttpHandler
熟悉WebForm开发的朋友一定都知道,Page类必须实现一个接口,就是IHttpHandler.HttpHandler是一个HTTP请求的真正处理中心,在HttpHandler容器中,ASP.NET ...
- 【深入浅出jQuery】源码浅析2--奇技淫巧
最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...
- 浅析匿名函数、lambda表达式、闭包(closure)区别与作用
浅析匿名函数.lambda表达式.闭包(closure)区别与作用 所有的主流编程语言都对函数式编程有支持,比如c++11.python和java中有lambda表达式.lua和JavaScript中 ...
- word-break|overflow-wrap|word-wrap——CSS英文断句浅析
---恢复内容开始--- word-break|overflow-wrap|word-wrap--CSS英文断句浅析 一 问题引入 今天在再次学习 overflow 属性的时候,查看效果时,看到如下结 ...
随机推荐
- Ubuntu 如何将桌面上的Home中的文件夹除去
安装Ubuntu后, 由于无法用Terminal(终端)进入带中文的文件夹,会引起很多操作不便.很多朋友想到了将它们都改成中文,但是当再次开机重启使却会发现,原本光洁的桌面现在竟然出现了一堆文件夹?? ...
- 2018.8.17 关于JavaScript的几种常见的全局函数
JavaScript常见的全局函数 <!doctype html> <html lang="en"> <head> <meta chars ...
- 旋度定理(Curl Theorem)和散度定理(Divergence theorem)
原文链接 首先说说格林公式(Green's theorem).对于一段封闭曲线,若其围城的区域D为单连通区域(内部任意曲线围城的区域都属于院区域),则有如下公式: 其中其中L为D的边界,取正方向.如果 ...
- SQL之Case when 语句
--case简单函数 (把多列变成单列) ' then '女' when ' then '男' else '其他' end from [Northwind].[dbo].[Users] --case搜 ...
- youku服务端
文件结构 config import os IP_PORT = () BACKLOG = BASE_DIR = os.path.dirname(os.path.dirname(__file__)) B ...
- Linux更改ssh端口号,很easy!
因为公司业务需求,可能涉及到更改ssh远程的端口号,用下面方法轻松解决,废话不多说! 1.打开ssh端口配置文件:vim /etc/ssh/sshd_config,找到如下图所示的端口,改为自己想改的 ...
- Linux问题分析或解决_ssh无法连接
1. ldd - 检查依赖库是否存在问题 问题:ssh连接不上,之前一直没有问题,最近别人安装了其他桌面(系统Ubuntu) 解决: 查看进程,发现没有启动 ps -ef | grep ssh 重新安 ...
- 区分js中的null,undefined,"",0和false
console.log(typeof null);//object console.log(typeof undefined);//undefined console.log(typeof " ...
- scrapy--cnblogs
之前一直在学习关于滑块验证码的爬虫知识,最接近的当属于模拟人的行为进行鼠标移动,登录页面之后在获取了,由于一直找不到滑块验证码的原图,无法通过openCV获取当前滑块所需要移动的距离. 1.机智如我开 ...
- tcl之其他命令-eval/source