我猜大家对于生成器肯定并不陌生,但是为了能让我愉快的继续装逼,我们还是用点篇幅讲一下什么是生成器吧。比如在 Python 里,我们想生成一个范围 (1,100000) 的一个 list,于是我们无脑写了如下的代码出来
注1:这里有同学提出了为什么我们不直接返回 range(start,stop) ,Nice question,这里涉及到一个基础问题, range 的机制究竟是怎样的。这就要分版本而论了,在 Python 2.x 的版本中, range(start,stop)其实本质上是预先生成一个 list ,而 list 对象是一个 Iterator ,因此可以被 for 语句所使用。
然后在 Python 2.x 中还有一个语句叫做 xrange ,其生成的是一个 Generator 对象。
在 Python 3 中事情发生了一点变化,可能社区觉得 range 和 xrange 分裂太过蛋疼,于是将其合并,于是现在在 Python 3 中,取消了 xrange 的语法糖,然后 range 的机制也变成生成一个 Generator 而不是list
但是大家考虑过一个问题么,如果我们想生成数据量非常大,预先生成数据的行为无疑是很不明智的,这样会耗费大量的内存。于是 Python 给我们提供了一种新的姿势,Generator (生成器)。
是的,Generator 其中一个特性就是不是一次性生成数据,而是生成一个可迭代的对象,在迭代时,根据我们所写的逻辑来控制其启动时机。
Generator 深入这里可能有一个问题,大家肯定想问 Python 开发者们不可能为了这一种使用场景而去单独创建一个 Generator 机制吧,那么我们 Generator 还有其余的使用场景么。当然,请看标题,对了嘛,Generator 另一个很大作用可以说就是当做协程使用。不过在这之前,我们要去深入的了解下 Generator 才能方便我们后面的讲解。
Generator 内建方法关于 Python 中可迭代对象的一点背景知识
首先,我们来看看 Python 中的迭代过程。
在 Python 中迭代有两个概念,一个是 Iterable ,另一个是 Iterator 。让我们分别来看看。
第N次首先,Iterable 近似的可以理解成为一个协议,判断一个 Object 是否是 Iterable 的方法就是看其实现了 iter与否,如果实现了 iter ,那么这便可以认为是一个 Iterable 对象。空谈误国,实干兴邦,让我们直接来看一段代码理解下:
好了,让我们来看看上面这段代码里发生了什么,首先 for 语句的引用首先去判断迭代的是 Iterable 对象还是Iterator 对象,如果是实现了 __iter__ 方法的对象,那么就是一个 Iterable 对象, for 循环首先调用对象的__iter__ 方法来获取一个 Iterator 对象。那么什么是 Iterator 对象呢,这里可以近似的理解为是实现了 next() 方法(注:在Python3中是 next 方法)。
OK,让我们继续回到刚刚说到的那里,在上面的代码中 for 语句首先判断是一个 Iterable 对象还是 Iterator 对象,如果是 Iterable 对象那么调用其 iter 方法来获取一个 Iterator 对象,接着 for 循环会调用 Iterator 对象中的next() (注:Python3 里是 __next__ )方法来进行迭代,直到迭代过程结束抛出 StopIteration 异常。
让我们先看看前面那段代码吧:
首先我们要确定一点的是 Generator 其实也是一个 Iterator 对象。OK 让我们来看看上面这段代码,首先 for 确定 generateList1 是一个 Iterator 对象,然后开始调用 next() 方法进行进一步迭代。OK 此时你肯定想问这里面 next() 方法是怎样让 generateList1 进一步往下迭代的呢?答案在于 Generator 的内建 send() 方法。我们还是来看一段代码。
这里我们应该输出什么?答案就是 0,1,2,3,4 ,结果上和我们用 for 循环进行运算的结果是不是一样。好了,我们现在可以得出一个结论就是:
Generator 迭代的本质就是通过内建的 next() 或 __next__() 方法来调用内建的 send() 方法。
继续吐槽内建方法前面我们提到一个结论 :
Generator 迭代的本质就是通过内建的 next() 或 __next__() 方法来调用内建的 send() 方法。
现在我们来看个例子:
注2:有同志问:“这里没想明白,c.send(3) 是 相当于 yield n 返回了个 3 给 newvalue ?”,好的,nice question,其实这个问题我们看前面之前的代码运行图就知道, c.send(3) 首先,将 3 赋值给 newvalue,然后程序运行剩下的代码,直到遇到下一个 yield 为止,那么在这里,我们运行剩下完代码,在遇到yiled n 之前,将 n 的值已经改变为 3 ,接着, yield n 即约等于 return 3 。接着 countdown 这个Generator 将所有变量的状态冻结,然后静静的呆在内存中,等待下一次的 next 或 __next__() 方法或者
是 send() 方法的唤醒。
小贴士:我们如果直接调用 send() 的话,第一次请务必 send(None) 只有这样一个 Generator 才算是真正被激活了。我们才能进行下一步操作。
Coroutines are computer program components that generalize subroutines for nonpreemptive multitasking,by allowing multiple entry points for suspending and resuming execution at certain locations. Coroutines are wellsuited for implementing more familiar program components such as cooperative tasks, exceptions,event loop, iterators, infinite lists and pipes.
According to Donald Knuth, the term coroutine was coined by Melvin Conway in 1958, after he applied it to construction of an assembly program.[1] The first published explanation of the coroutine appeared later, in 1963.
简而言之,协程是比线程更为轻量的一种模型,我们可以自行控制启动与停止的时机。在 Python 中其实没有专门针对协程的这个概念,社区一般而言直接将 Generator 作为一种特殊的协程看待,想想,我们可以用 next 或__next__() 方法或者是 send() 方法唤醒我们的 Generator ,在运行完我们所规定的代码后, Generator 返回并将其所有状态冻结。这是不是很让我们 Excited 呢!!
课后作业现在我们要后序遍历二叉树,我知道看这篇文章神犇们都能无脑写出来的,让我们看看代码先:
但是,我们知道递归深度太深的话,我们要么爆栈要么 py 交易失败,OK ,Generator 大法好,把你码农平安保。
还是直接看代码:
原文发布时间为:2016-11-17
本文作者:李者璈
本文来自云栖社区合作伙伴“Python中文社区”,了解相关信息可以关注“Python中文社区”微信公众号