python(23): 发生器和循环器(14.6)

好久没有看书了。先是“两会”开了,我也像代表委员们一样忙,没有心情写。后来又是人一贯的惰性在起作用。以至于到了月底了,才觉得应该再看看书。还好对于是月份来说也算是有始有终。
发生器函数其实也是一种函数,用def来定义。当然跟一般的函数是不一样的。首先,普通函数既可以用def来定义,也可以用lambda来定义。而“发生器函数”应该只能用def来定义。其次,一般的函数在被调用的时候会用return语句返回一个值,然后退出。而发生器在被调和的时候,会返回一个叫做“发生器”类型的对象。
现在想象一下,用def定义了一个函数。那么,解释器怎么知道这是一个发生器函数呢。其实也很简单,一个发生器函数必然会包含一个或多个yield语句,当解释器碰到这样的函数时,就会知道这是一个“发生器函数”。下面通过一个简单的例子来了解具体的运行情况:
  • def gen(x):
    •     for i in range(x)[2:]:
    •         yield i
    •         yield i**2
    • x = gen(5)
    • x.next()===>2
    • x.next()===>4
    • x.next()===>3
      解释器在运行这段程序的时候,首先可以断定这是一个“发生器函数”,因此x这时为一个“发生器”类型的对象(generator object)。 这种对象具有一个next()方法,用来获取相应的值。这里就可以看到发生器的最大特点。看第6行,当运行到这行的时候,对应的是gen函数中的第3行,取得值2。这里解释一下,x取值5,因此range(x)为[0,1,2,3,4],而range(x)[2:]就为[2,3,4]。来看第7行,当next方法再次被调用的时候,gen并不是从头开始执行,它是从上一个yield语名后开始执行,也就是第4行,所以得值4。可以看出,next方法就相当于让yield函数执行到下一条yield语句,如果没有下一条yield语句了,那么就返回StopIteration异常。
      在上面的例子中,我们使用了next方法来显式地控制“发生器函数”一个一个地“发生”值。其实发生器函数更多的时候是用在”循环“的语境中,比如for语句、map()函数、list再构造等。当使用在这些语句中时,并不需要显式地调用next方法。 比如:
    • for i in x:
    •     print i
      下面来说说”循环器“。循环器也是一种对象,它由内置于函数iter()产生。这种对象也有一个next方法。作用与”发生器“对象中的next相同。由于在python中,一切皆对象。比如D为一个dict,iter(D)实际上只是调用了D中的__iter__函数。所以,如果自己定义一个类,只要这个类有__iter__函数,能产生”循环器“对象,那么这个类的对象就完全可以使用在像for语句这样的循环语境中。
      最后说一句,发生器实际达的效果是返回一组值。这种功能可以使用return语句返回一个序列(list dict tuple)来实现。但是如果当这个序列太大时,相信用”发生器“是一个更好的选择。因为,这样的话就没有必要把一大堆的东西放到内存中。