1、生成器初识。
Python 使用生成器对「延迟操作」提供了支持,所谓「延迟操作」就是在需要它的时候才产生结果,而不是说立即产生结果。
在 Python 中,定义生成器必须要使用 yield 这个关键词,yield 翻译成中文有「生产」这方面的意思。在 Python 中,它作为一个关键词,是生成器的标志。接下来我们来看一个例子:
>>> def f():
... yield 0
... yield 1
... yield 2
...
>>> f
<function f at 0x00000000004EC1E0>
上面是写了一个很简单的 f 函数,代码块是 3 个 yield 发起的语句,下面让我们来看看如何使用它:
>>> fa = f()
>>> fa
<generator object f at 0x0000000001DF1660>
>>> type(fa)
<class 'generator'>
上述操作可以看出,我们调用函数得到了一个生成器(generator)对象。
>>> dir(fa)
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__',
'__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
在上面我们看到了 __iter__() 和 __next__(),虽然我们在函数体内没有显示的写 __iter__() 和 __next__(),仅仅是写了 yield,但它就已经是「迭代器」了。既然如此,那我们就可以进行如下操作:
>>> fa = f()
>>> fa.__next__()
0
>>> fa.__next__()
1
>>> fa.__next__()
2
>>> fa.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
从上面的简单操作可以看出:含有 yield 关键词的函数 f() 是一个生成器对象,这个生成器对象也是迭代器。所以就有了这样的定义:把含有 yield 语句的函数称为生成器,生成器是一种用普通函数语法定义的迭代器。
通过上面的例子可以看出,这个生成器(即迭代器)在定义的过程中并没有昨天讲的迭代器那样写 __iter__(),而是只用了 yield 语句,之后一个普普通通的函数就神奇的成了生成器,同样也具备了迭代器的特性。
yield 语句的作用,就是在调用的时候返回相应的值。
参考:Python生成器初识。
2、生成器延伸。
Python 其实有两种不同的方法来提供生成器,一种是函数形式,另一种是表达式形式,说全一点儿就是「生成器函数」和「生成器表达式」。
- 生成器函数。
「生成器函数」和普通的函数定义类似。区别在于普通函数使用 return 返回结果,生成器函数是用 yield 返回结果。
yield 的作用是在调用的时候返回相应的值,一次返回一个结果,在每个结果中间挂起函数的状态(即暂停执行),下一次执行是从上次暂停的位置开始,继续向下执行。
下面写出「将一个全是整数的列表进行操作后只保留奇数」的程序。
def get_odd(lst):
for i in lst:
if i % 2:
yield i
def main():
lst = range(10)
for i in get_odd(lst):
print(i)
if __name__ == '__main__':
main()
- 生成器表达式。
「生成器表达式」和列表推导式类似。区别在于使用列表推导,一次会产生所有的结果,而用「生成器表达式」则不会这样,它是按需产生。
列表推导式的写法如下:
>>> res = [x for x in range(5)]
>>> res
[0, 1, 2, 3, 4]
生成器表达式就是将上面的 [] 变成 () 即可:
>>> res = (x for x in range(5))
>>> res
<generator object <genexpr> at 0x109d9f570>
>>> next(res)
0
>>> next(res)
1
>>> next(res)
2
>>> next(res)
3
「生成器」的优势在「生成器表达式」中体现在,如果我们想对一系列整数求和,直接用生成器可以写成下面这样:
>>> sum(x for x in range(5))
10
但是如果你用常规的写法去写,就会写成下面这样:
>>> sum([x for x in range(5)])
10
上面的代码先构造了一个列表,然后再用 sum 函数求和,多了一步。光在时间效率上,就已经差别巨大。
参考,生成器延伸。
3、总结
综上所讲,「生成器」光在明面上的优点就有好几个:代码行数更少;代码更易读;时效更高。