Python generator 与 yield 关键字

初次使用 tornado 开发服务器, 也进一步学习使用 Python 这门语言.
开始对 Python 的语法糖 decorator 感兴趣, tornado 的异步事件
处理使用了 tornado.gen.coroutine decorator, 要求代码中异步
执行的代码需要添加 yield 关键字在前面.
于是开始补基础, 使用 yield 这个关键字前需要先了解 Generator:

Python Generators 生成器, 参考自 Generators 文档

首先看简单的代码, 计算一个 100w 长度数组的和:

代码目的很明显, 而这里有个问题是, 100w 的整形数据
在内存中也不够用(假设自己的机器是4G内存).

重新调整下代码:

这样以可迭代的模式实现 generator, 不过该实现有以下问题:

它有很多引用
使用复杂的方式表现逻辑

那么使用另一种方式: 更简短, 使用生成器函数

使用 yield 关键字, 逻辑清晰自然. 相比于 list,
内存有了节省, 空间复杂度O(1).
注: Python 2 版本的内建 xrange() 是相同功能的,
Python 3 range() 已经是一个 generator.

看以下代码:

现在我们再玩一个小例子, 首先开始写的代码(摘抄自 wiki文档):

当迭代到 最后一个元素(5)之后, 抛出了StopIteration异常,
原因是我理解的 generator 的 next 函数有误, 搜索到Stackoverflow
其描述是:

If you are using Python 2.6+ you should use the next
built-in function, not the next method (which was
replaced with next in 3.x). The next built-in takes
an optional default argument to return if the iterator
is exhausted, instead of raising StopIteration:

next((i for i, v in enumerate(a) if i == 666), None)

我的版本是 Python 2.7, 需要使用的是内建的 next()函数, 定义None参数
作为迭代器迭代最后一次之后返回参数, 即把上面的注释代码替换前一行即可.
i = next(ret, None) 替代 i = ret.next()
或者使用 for .. in … 取代 while:

使用 PyCharm 调试, 可以观察代码的执行顺序,
以 while 循环代码解释:
1. 首先循环外获取 generator, 赋值给 ret

2. 进入循环, next 获取第一个值.

3. next 返回值, 如果 i 非 None, 执行print i
否则 break, 跳出循环.

4. 在返回循环代码第一行, 执行next

5. 此时 ret 生成器的代码块是在 yield 关键字处挂起的,
继续执行之后的代码块, 即 s.add(x) 操作, 然后继续在
for … in … 中运行, 直到遇见 yield 关键字, 再返回 x*2
跳到步骤 3. 如果生成器里的 for … in … 遍历完成,
其实在步骤3中返回 None 之前的 next 调用里, 执行了
generator 最后的代码, 即打印 s, 然后 ret generator
返回None

Generator 的使用场景的例子多是在迭代上的例子上,
tornado框架使用其特性作为异步执行的一部分. 具体的
异步实现原理还需要跟进源码框架阅读. 而易于理解的是
yield 关键字可以挂起函数, 以 lazy 的方式等待下一
次执行. tornado 利用这个特性, 在异步的操作完成后,
再次调用时, 执行之后的代码块.


补充

2014-10-06 新浪微博 @程序员的那些事 上有一篇分享文章关于
yield 关键字的文章, 从中加深了对其使用的理解. 其文章最开始
引用自 IBM 的 developerworks 中文文档 Python yield 使用浅析 .

总结该文章里的收获:
1. 从斐波那契的函数的例子收获使用的模块和获知的属性:

fab 是 generator function
我们通过 from inspect import isgeneratorfunction
isgeneratorfunction(fab) 返回值是 True

而 fab(5) 返回的是 generator class

判断是否可迭代:

2. ” 每次调用 fab 函数都会生成一个新的 generator 实例, 各实例互不影响 “

3. return 的作用

在一个 generator function 中, 如果没有 return,
则默认执行至函数完毕, 如果在执行过程中 return,
则直接抛出 StopIteration 终止迭代.

原创文章,转载请注明: 转载自kaka_ace's blog

本文链接地址: Python generator 与 yield 关键字

发表评论

电子邮件地址不会被公开。 必填项已用*标注