打开主菜单

生成器(generator)是一种返回一个值的迭代器,每次从该迭代器取下一个值。

生成器表达式(generator expression)类似列表解析(list comprehension)表达式的语法,只不过把列表解析的[]换成(),但是它返回的是一个生成器对象而不是列表对象。

生成器其实是一种特殊的迭代器,不过这种迭代器更加优雅。它不需要迭代器的类一样写__iter__()和__next__()方法了,只需要一个yield关键字。 生成器一定是迭代器(反之不成立),因此任何生成器也是以一种惰性加载的模式生成值。

基于yield的生成器编辑

Python 2.2的PEP 255开始引入生成器。函数如果包含yield指令,该函数的返回值是一个生成器对象,此时函数体中的代码并不会执行,只有显示或隐示地调用next的时候才会真正执行里面的代码。yield可以暂停一个函数并返回此时的中间结果。该函数将保存执行环境并在下一次恢复。

生成器中,如果没有return语句,则执行到函数完毕时将返回StopIteration异常。如果在执行过程中遇到return语句,则直接抛出StopIteration异常,终止迭代。如果在return后返回一个值,那么这个值作为StopIteration异常的说明,不是程序的返回值。

可用close()手动关闭生成器函数,后面再调用生成器会直接返回StopIteration异常。

生成器函数最大的特点是可以接受通过send()从外部传入的一个值,并根据该值计算结果后返回。这是生成器函数最难理解的地方,也是最重要的地方,协程的实现依赖于此。语法为

 receive=yield value

其中receive将收到send方法中的参数的值。在启动生成器函数时只能send(None),如果试图输入其它的值都会得到错误提示信息。

throw()用来向生成器函数送入一个异常。生成器内部可以捕捉、处理此异常,也可以不处理此异常。

基于yield from的生成器编辑

Python3.3版本的PEP 380中添加了yield from语法,允许一个生成器将其部分操作委派给另一个生成器。

基于yield的生成器的局限性在于只能向它的直接调用者yield值。例如:

def g(x):
    yield 1
    yield 2

def foo():
    iterator = g(0)
    print(next(iterator))

if __name__ == '__main__':
    next(foo()) # 错误,只能直接调用生成器

这意味着那些包含yield的代码不能想其他代码那样被分离出来放到一个单独的函数中,让外界去调用这个函数。这也正是yield from要解决的。yield from为调用者和子生成器之间提供了一个透明的双向通道,包括从子生成器获取数据以及向子生成器传送数据。

虽然yield from主要设计用来向子生成器委派操作任务,但yield from可以向任意的迭代器委派操作。

yield from iterable本质上等于for item in iterable: yield item的缩写版。例如:

def g(x):
     yield from range(x, 0, -1)
     yield from range(x)

list(g(5)) #结果为[5, 4, 3, 2, 1, 0, 1, 2, 3, 4]

不同于普通的循环,yield from允许子生成器直接从调用者接收其发送的信息或者抛出调用时遇到的异常,并且返回给委派生产器一个值。如果对子生成器的调用产生StopIteration异常,委派生成器恢复继续执行yield from后面的语句;若子生成器产生其他任何异常,则都传递给委派生成器。如果GeneratorExit异常被抛给委派生成器,或者委派生成产器的close()方法被调用,如果子生成器有close()的话也将被调用。当子生成器结束并抛出异常时,yield from表达式的值是其StopIteration异常中的第一个参数。例如:

def accumulate():    # 子生成器,将传进的非None值累加,传进的值若为None,则返回累加结果
    tally = 0
    while 1:
        next1 = yield
        if next1 is None:
            return tally
        tally += next1

def gather_tallies(tallies):    # 外部生成器,将累加操作任务委托给子生成器
    while 1:
        tally = yield from accumulate()
        tallies.append(tally)

 
tallies = []
acc = gather_tallies(tallies)
next(acc)    # 使累加生成器准备好接收传入值
for i in range(4):
    acc.send(i)

acc.send(None)
for i in range(5):
    acc.send(i)
    
acc.send(None)
print(tallies) #输出[6,10]