(windows) Python 2.7 学习笔记 十九 高级特性 – 生成器

生成器

通过列表生成式,可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含 100 个元素的列表,不仅占用很大的存储空间,如果仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的 list,从而节省大量的空间。在 Python 中,这种一边循环一边计算的机制,称为生成器(Generator)。

要创建一个 generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的 [] 改成 (),就创建了一个 generator

创建 Lg 的区别仅在于最外层的 []()L 是一个 list,而 g 是一个 generator

可以直接打印出 list 的每一个元素,但怎么打印出 generator 的每一个元素呢?

如果要一个一个打印出来,可以通过 generatornext() 方法:

generator 保存的是算法,每次调用 next(),就计算出下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出 StopIteration 的错误。

当然,上面这种不断调用 next() 方法实在是太麻烦了,正确的方法是使用 for 循环,因为 generator 也是可迭代对象:

所以,创建了一个 generator 后,基本上永远不会调用 next() 方法,而是通过 for 循环来迭代它。

generator 非常强大。如果推算的算法比较复杂,用类似列表生成式的 for 循环无法实现的时候,还可以用函数来实现。

比如,著名的斐波那契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

1,1,2,3,5,8,13,21,34,…

斐波那契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

上面的函数可以输出斐波那契数列的前 N 个数:

仔细观察,可以看出,fib 函数实际上是定义了斐波那契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似 generator

也就是说,上面的函数和 generator 仅一步之遥。要把 fib 函数变成 generator,只需要把 print b 改为 yield b 就可以了:

这就是定义 generator 的另一种方法。如果一个函数定义中包含 yield 关键字,那么这个函数就不再是一个普通函数,而是一个 generator

这里,最难理解的就是 generator 和函数的执行流程不一样。函数是顺序执行,遇到 return 语句或者最后一行函数语句就返回。而变成 generator 的函数,在每次调用 next() 的时候执行,遇到 yield 语句返回,再次执行时从上次返回的 yield 语句出继续执行。

举个简单的例子,定义一个 generator,依次返回数字 1,3,5:

可以看到,odd 不是普通函数,而是 generator,在执行过程中,遇到 yield 就中断,下次又继续执行。执行 3yield 后,已经没有 yield 可以执行了,所以,第 4 次调用 next() 就报错。

回到 fib 的例子,在循环过程中不断调用 yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。

同样的,把函数改成 generator 后,基本上从来不会用 next() 来调用它,而是直接使用 for 循环来迭代:

generator 是非常强大的工具,在 Python 中,可以简单地把列表生成式改成 generator,也可以通过函数实现复杂逻辑的 generator

要理解 generator 的工作原理,它是在 for 循环的过程中不断计算出下一个元素,并在适当的条件结束 for 循环。对于函数改成的 generator 来说,遇到 return 语句或者执行到函数体最后一行语句,就是结束 generator 的指令,for 循环随之结束。

发表评论

zh_CNChinese
zh_CNChinese