最近在读《流畅的python》,读书的好处是它可以帮你把混乱的概念屡清楚。这篇文章再谈几个概念:生成器、生成器函数、生成器表达式。
生成器是一个对象,是一个实例化的东西,等同于迭代器。
生成器函数是个函数,函数体含有yield表达式。
>>> def gen_123():
... yield 1
... yield 2
... yield 3
...
>>> gen_123
<function gen_123 at 0x0243D570>
>>> gen_123()
<generator object gen_123 at 0x02424A08>
从生成器函数可以得到一个生成器,而根据上一篇文章,生成器就是一个迭代器,不断的应用next()能遍历每一个元素,最终得到StopIteration异常。遍历出来的元素就是生成器函数里面yield的内容。
那什么是生成器表达式呢?
书中是从列表推导引出这个概念的,并介绍了“惰性实现”的概念。
下面是书中14-8的例子
>>> def gen_AB(): #①
... print('start')
... yield 'A'
... print('continue')
... yield 'B'
... print('end.')
...
>>> res1 = [x*3 for x in gen_AB()] #②
start
continue
end.
>>> for i in res1: #③
... print('-->',i)
...
('-->', 'AAA')
('-->', 'BBB')
>>> res2 = (x*3 for x in gen_AB()) #④
>>> res2 #⑤
<generator object <genexpr> at 0x02264AA8>
>>> for i in res2: #⑥
... print('-->',i)
...
start
('-->', 'AAA')
continue
('-->', 'BBB')
end.
>>>
res1是列表推导的结果,列表推导迫不及待地把生成器gen_AB()先遍历了,所以输出了start、congtinue和end,得到的结果res1是一个list对象。
res2是生成器表达式的结果,生成器表达式仅输出生成器,不遍历生成器。这就是“惰性”的意思,把遍历操作留给程序员,就是⑥那里的for循环。
其实生成器表达式和生成器函数都是为了得到生成器,只不过使用生成器表达式可以省去写生成器函数!
那么何时使用生成器函数,何时使用生成器表达式呢?
作者在14.7节给出了答案,直接引用过来:
遇到简单的情况时,可以使用生成器表达式,因为这样扫一眼就知道代码的作用。
根据我的经验,选择使用哪种句法很容易判断:如果生成器表达式要分成很多行写,我倾向于定义生成器函数,以便提高可读性。此外,生成器函数有名称,可以重复使用。