通过List Comprehensions,我们可以很方便的生成一个列表,但是受限于内存等原因,列表的长度是有限制的,例如我们创建一个100w长度的列表,既浪费了空间,又如果我们只需要部分数据的话,那么大部分空间就被浪费了。
所以,如果列表中的元素可以按照某种固定的算法推导出来,我们就可以在迭代中不断地通过这个算法计算出后面的值,这在python中叫做生成器(generator)。
生成器
带有 yield 关键字的的函数在 Python 中被称之为 generator(生成器)。Python 解释器会将带有 yield 关键字的函数视为一个 generator 来处理。一个函数或者子程序都只能 return 一次,但是一个生成器能暂停执行并返回一个中间的结果 —— 这就是 yield 语句的功能 : 返回一个中间值给调用者并暂停执行。
创建一个generator:
|
|
可以用过next方法去访问生成器中的元素:
|
|
当访问到最后一个元素之后再次访问的时候,会引起StopIteration的报错。
我们来看看next函数:
|
|
所以从本质上来说,生成器还是一个迭代器。当然在日常中我们会更多的用for循环来遍历。
前面讲过,实际上生成器保存的是一个算法,而不是一个值。让我们来看下面的一个函数:
|
|
在函数中使用yield,这个函数就变成了一个生成器,我们看一下输出:
|
|
会发现实际上是每次都卡住了,我们再看一个斐波那契数列的例子:
|
|
输出:
|
|
这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
但是在调用生成器的时候,会发现拿不到最后return的值,要获取这个值必须捕获StopIteration的错误,如下代码中:
|
|
执行结果:
|
|
生成器表达式
生成器表达式是列表解析的拓展,列表解析的不足在于它必须一次性生成所有的数据用于创建对象,所以不适合用于迭代大量的数据。生成器表达式通过结合列表解析和生成器来解决这个问题:
- 列表解析: [expr for iter_var in iterable if cond_expr]
- 生成器: (expr for iter_var in iterable if cond_expr)
两者的语法很相似,区别在于生成器返回的是一个生成器对象,而列表解析返回的是一个列表。
我们来看一个读取文件的实例,我们读取一个文件,选择出其中最长的一行并返回长度:
|
|