python中迭代是一种重要的方式,使得代码简洁,再加上列表解析等,大大增强迭代对应的表达语义。

然而自己手动写迭代器并非很简洁,需要定义类,定义__iter__()以及next()方法,而生成器的产生正式基于对语义简化表达的优化。

生成器

在语法上,生成器很像一个带yield语句的函数,不过与普通函数不同的是,生成器可以通过yield语句暂停执行并返回中间结果,当下次调用next()时,会从前一次离开的地方继续执行。

yield表达式语法:

yield_atom           ::=  "(" yield_expression ")"
yield_expression     ::=  "yield" [expression_list]
expression_list      ::=  expression_r( "," expression )* [","]

注:

  1. 生成器执行过程中,主动执行的yield表达式(yield_atom),除了向外界返回expression_list外,yield_atom本身也有返回值,默认为None。
  2. 当通过gen.send(value)向生成器发送数据时,value将成为yield表达式本身的返回值。

生成器方法:

next()方法使得生成器可以用于for x in gen来迭代。

例1,随机从列表中弹出元素:

def random_pop( List ):
    import random
    while len(List) > 0:
        yield List.pop( random.randint( 0, len(List)-1 ) )
 
>>> [ x for x in random_pop( range(10) ) ]
[1, 3, 6, 9, 4, 8, 0, 5, 7, 2]

例2,无限的Fibonacci序列:

def fibonacci():
    a, b = 0, 1
    while True:
        yield b
        a, b = b, a + b
 
>>> fib = fibonacci()
>>> [ fib.next() for x in range(10) ]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

如果调用send()来启动生成器,参与需要为None,因为当前没有可以接受该值的yield语句。

例如:

def counter():
    count = 0
    while True:
        val = (yield count)      # send()设置值val,否则val为None
        if val is not None:
            count = val
        else:
            count += 1
 
>>> cnt = counter()
>>> cnt.next()
0
>>> cnt.next()
1
>>> cnt.send(10)
10
>>> cnt.next()
11

例如:

def echo( value ):
    print u'start'
    try:
        while True:
            try:
                value = (yield value)   
            except Exception, e:
                value = e
    finally:
        print u'clean up'
 
>>> gen = echo(5)
>>> print gen.next()
start
5
>>> print gen.next()
None
>>> print gen.send(10)
10
>>> gen.throw( TypeError, u'spam' )
TypeError(u'spam',)
>>> gen.close()
clean up

生成器表达式

在语法上:

生成器表达式是可迭代的对象,并且是缓式计算,是优化版的列表解析。

例如:

>>> max( len(x) for x in ['sjw', 'suninf'] )
6
 
>>> [ x for x in ( y**2 for y in range(5) ) ]
[0, 1, 4, 9, 16]