生成器
生成器的作用
每次只生成一个值,而不是直接创建大的元组,节省内存消耗。
第一个例子
先看一个交互式例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| >>> def make_counter(x): ... print('entering make_counter') ... while True: ... yield x ... print('incrementing x') ... x = x + 1 ... >>> counter = make_counter(2) >>> counter <generator object at 0x001C9C10> >>> next(counter) entering make_counter 2 >>> next(counter) incrementing x 3 >>> next(counter) incrementing x
|
make_counter
中出现的 yield 命令的表示这不是一个普通的函数,而是一个一次生成一个值的特殊类型函数。调用该函数将返回一个可用于生成连续 x 值的 生成器【Generator】 。
为创建 make_counter
生成器的实例,仅需像调用其它函数那样对它进行调用。注意该调用并不完全执行函数里的指令,由上例可见它并不指定第一行的print
指令。
next()
函数以一个生成器对象为参数,并返回其下一个值。对 counter 生成器第一次调用 next()
,它针对第一条 yield 语句执行 make_counter()
中的代码,然后返回所产生的值。在此情况下,该代码输出将为 2,因其仅通过调用 make_counter(2)
对生成器进行初始创建。
对同一生成器对象反复调用 next()
将确切地从上次调用的位置开始继续,直到下一条 yield
语句。所有的变量、局部数据等内容在 yield
时被保存,在 next()
时被恢复 。下一行代码等待被执行以调用 print()
打印出 incrementing x
。之后,执行语句 x = x + 1。然后它继续通过 while
再次循环,而它再次遇上的第一条语句是 yield x
,该语句将保存所有一切状态,并返回当前 x 的值。
yield
暂停一个函数。next()
从其暂停处恢复其运行。
由于 `make_counter` 设置了一个无限循环,理论上可以永远执行该过程,它将不断递增 x 并输出数值。还是让我们看一个更加实用的生成器用法。
### 斐波那契生成器 ###
1 2 3 4 5
| def fib(max): a, b = 0, 1 while a < max: yield a a, b = b, a + b
|
斐波那契序列是一系列的数字,每个数字都是其前两个数字之和。它从 0 和 1 开始,初始时上升缓慢,但越来越快。启动该序列需要两个变量:从 0 开始的 a,和从 1 开始的 b 。
a 是当前序列中的数字,因此对它进行 `yield` 操作。b 是序列中下一个数字,因此将它赋值给 a,但同时计算下一个值 (a + b) 并将其赋值给 b 以供稍后使用。
因此,现在有了一个连续输出斐波那契数值的函数。当然,还可以使用递归来完成该功能,但这个方式更易于阅读。同样,它也与 `for` 循环合作良好。
1 2 3 4 5 6
| >>> from fibonacci import fib >>> for n in fib(1000): ... print(n, end=' ') 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 >>> list(fib(1000)) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]
|
可以在 for 循环中直接使用像 fib() 这样的生成器。**for 循环将会自动调用 next() 函数,从 fib() 生成器获取数值并赋值给 for 循环索引变量。**
## 生成器表达式 ##
生成表达式类似生成器函数,只不过它不是函数。
#### 示例 ####
1 2 3 4 5 6 7 8 9 10
| >>> unique_characters = {'E', 'D', 'M', 'O', 'N', 'S', 'R', 'Y'} >>> gen = (ord(c) for c in unique_characters) >>> gen <generator object <genexpr> at 0x00BADC10> >>> next(gen) 69 >>> next(gen) 68 >>> tuple(ord(c) for c in unique_characters) (69, 68, 77, 79, 78, 83, 82, 89)
|
#### 说明 ####
* 生成器表达式类似一个yield值的匿名函数。表达式本身看起来像列表解析, 但不是用方括号而是用圆括号包围起来。
* 生成器表达式返回迭代器。
* 调用 next(gen) 返回迭代器的下一个值。
* 如果你愿意,你可以将生成器表达式传给`tuple()`, `list()`, 或者 `set()` 迭代所有的值并且返回元组,列表或者集合。在这种情况下,你不需要一对额外的括号 — 将生成器表达式 `ord(c) for c in unique_characters` 传给 `tuple()` 函数就可以了, Python 会推断出它是一个生成器表达式。
使用生成器表达式取代列表解析可以同时节省 cpu 和 内存。如果你构造一个列表的目的仅仅是传递给别的函数,(比如传递给tuple() 或者 set()), 用生成器表达式替代吧!