博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

python generator 生成器 send yield

Posted on 2015-12-10 18:07  bw_0927  阅读(1094)  评论(0)    收藏  举报

http://www.oschina.net/translate/improve-your-python-yield-and-generators-explained

协程与子例程

我们调用一个普通的Python函数时,一般是从函数的第一行代码开始执行,结束于return语句、异常或者函数结束(可以看作隐式的返回None)。一旦函数将控制权交还给调用者,就意味着全部结束。函数中做的所有工作以及保存在局部变量中的数据都将丢失。再次调用这个函数时,一切都将从头创建。 

对于在计算机编程中所讨论的函数,这是很标准的流程。这样的函数只能返回一个值,不过,有时可以创建能产生一个序列的函数还是有帮助的。要做到这一点,这种函数需要能够“保存自己的工作”。 

我说过,能够“产生一个序列”是因为我们的函数并没有像通常意义那样返回。return隐含的意思是函数正将执行代码的控制权返回给函数被调用的地方。而"yield"的隐含意思是控制权的转移是临时和自愿的,我们的函数将来还会收回控制权。

在Python中,拥有这种能力的“函数”被称为生成器,它非常的有用。

生成器(以及yield语句)最初的引入是为了让程序员可以更简单的编写用来产生值的序列的代码。 以前,要实现类似随机数生成器的东西,需要实现一个类或者一个模块,在生成数据的同时保持对每次调用之间状态的跟踪。引入生成器之后,这变得非常简单。

由于生成器的这种特性跟协程很像。每一次迭代之间,会暂停执行,继续下一次迭代的时候还不会丢失先前的状态。

为了支持用生成器做简单的协程,Python 2.5 对生成器进行了增强(PEP 342),该增强提案的标题是 “Coroutines via Enhanced Generators”。

有了PEP 342的加持,生成器可以通过yield 暂停执行和向外返回数据,也可以通过send()向生成器内发送数据,还可以通过throw()向生成器内抛出异常以便随时终止生成器的运行。

 

注意:在Python之外,最简单的生成器应该是被称为协程(coroutines)的东西。在本文中,我将使用这个术语。请记住,在Python的概念中,这里提到的协程就是生成器。Python正式的术语是生成器;协程只是便于讨论,在语言层面并没有正式定义。

 

处理无限序列

我们不可能返回包含从start到无穷的所有的素数的列表 (虽然有很多有用的应用程序可以用来操作无限序列)。看上去用普通函数处理这个问题的可能性比较渺茫。

普通函数只有一次返回结果的机会,因而必须一次返回所有的结果。

走进生成器

这类问题极其常见以至于Python专门加入了一个结构来解决它:生成器。一个生成器会“生成”值。

一个生成器函数的定义很像一个普通的函数,除了当它要生成一个值的时候,使用yield关键字而不是return。如果一个def的主体包含yield,这个函数会自动变成一个生成器(即使它包含一个return)。除了以上内容,创建一个生成器没有什么多余步骤了。

生成器函数返回生成器的迭代器。这可能是你最后一次见到“生成器的迭代器”这个术语了, 因为它们通常就被称作“生成器”。要注意的是生成器就是一类特殊的迭代器。作为一个迭代器,生成器必须要定义一些方法(method),其中一个就是__next__()。如同迭代器一样,我们可以使用next()函数来获取下一个值。

#!/usr/bin/python

def simple_generator_function():
        yield 1
        yield 2
        yield 3

for value in simple_generator_function():     #隐式调用next
        print(value)

our_generator = simple_generator_function()
print next(our_generator)            #显式调用next
print next(our_generator)
print next(our_generator)



输出
1
2
3
1
2
3

  

 

当一个生成器函数调用yield,生成器函数的“状态”会被冻结,所有的变量的值会被保留下来,下一行要执行的代码的位置也会被记录,直到再次调用next()。一旦next()再次被调用,生成器函数会从它上次离开的地方开始。如果永远不调用next(),yield保存的状态就被无视了。

如果生成器函数调用了return,或者执行到函数的末尾,会出现一个StopIteration异常。 这会通知next()的调用者这个生成器没有下一个值了(这就是普通迭代器的行为)。

def simple_generator_function():
        yield 1
        yield 2
        yield 3


our_generator = simple_generator_function()

for value in our_generator:
        print(value)

print next(our_generator)

输出: 1 2 3 Traceback (most recent call last): File "wrong_generator.py", line 14, in <module> print next(our_generator) StopIteration   

  

 

n = yield r    //yield,放弃本函数的执行,同时把r的值返回给调用者send()/next()。 n的值就是send(n)的参数值

r = c.send(n)    //r的值就是yield的参数值。  n的值会传递给yield
 
====================
http://www.cnblogs.com/coderzh/archive/2008/05/18/1202040.html
 如果某个函数包含了yield,这意味着这个函数已经是一个Generator
 
def addlist(alist):
        for i in alist:
                yield i + 1


alist = [1, 2, 3, 4]
for x in addlist(alist):    #隐式调用next()启动生成器
        print x,

输出2 3 4 5

  

def h():
        print 'to be brave'
        yield 5

h()           #没有调用next()启动生成器,所以输入空

  没有任何输出

上面的h()被调用后并没有执行,因为它有yield表达式,因此,我们通过next()语句让它执行。next()语句将恢复Generator执行,并直到下一个yield表达式处。

 

def h():
        print 'to be brave'
        yield 5

h().next()
h().next()
h().next()

输出三次 to be brave
  

因为每次h().next() 都是新产生的一个generator对象

 

def h():
        print 'to be brave'
        yield 5

c = h()
c.next()
c.next()
输出:

to be brave
Traceback (most recent call last):
File "yield.py", line 24, in <module>
c.next()
StopIteration

如果生成器函数调用了return,或者执行到函数的末尾,会出现一个StopIteration异常

  

next()让包含yield的函数(Generator)执行

 

next()和send()在一定意义上作用是相似的,区别是send()可以传递值给yield表达式,而next()不能传递特定的值,只能传递None进去。因此,我们可以看做
c.next() 和 c.send(None) 作用是一样的。

 

 

第一次调用时,请使用next()语句或是send(None),不能使用send发送一个非None的值,否则会出错的,因为没有yield语句(例如下面的例子第一条语句是print,无法接收)来接收这个值

def x():
        print 'baiii'
        m = yield 5   #返回5
        print m       #接收外部send()进来的值到m中   
        d = yield 12
        print 'reach here '

 

  •   

    c = x()       //创建新的生成器对象
    c.next()     //启动generator
    c.next()     //给yield表达式传一个None值

输出  

baiii
None

 

  • c = x()

    c.next()           /启动generator,运行到第一个yield处停止

    c.send('hi')     //从停止的yield处继续运行,并把值hi传过去

输出

baiii
hi

 

  • c = x()

    c.send('hi')    //出错,generator还没启动

输出

Traceback (most recent call last):
File "yield.py", line 36, in <module>
c.send('hi')
TypeError: can't send non-None value to a just-started generator

 

5. send(msg) 与 next()的返回值

send(msg) 和 next()是有返回值的,它们的返回值很特殊,返回的是下一个yield表达式的参数。比如yield 5,则返回 5 。到这里,是不是明白了一些什么东西?

本文第一个例子中,通过for i in alist 遍历 Generator,其实是每次都调用了alist.Next(),而每次alist.Next()的返回值正是yield的参数,即我们开始认为被压进去的东东。我们再延续上面的例子:

1:def h():
2:    print 'Wen Chuan',
3:    m = yield 5 
4:    print m
5:    d = yield 12
6:    print 'We are together!'

c = h()         #创建一个生成器对象
m = c.next()  # 启动生成器;输出‘Wen Chuan’; 执行到第三行,暂停,返回5;外部的m 获取的值是yield 5 的参数值 5
d = c.send('Fighting!')    #继续执行生成器;接着从第三行开始执行,m的值是send进来的"Fighting"; 执行到第五行,暂停,返回12; d 获取了yield 12 的参数值12
print 'We will never forget the date', m, '.', d

输出结果:
Wen Chuan Fighting!
We will never forget the date 5 . 12

 

 

6. throw() 与 close()中断 Generator

中断Generator是一个非常灵活的技巧,可以通过throw抛出一个GeneratorExit异常来终止Generator。Close()方法作用是一样的,其实内部它是调用了throw(GeneratorExit)的。我们看:

def x():
        print 'bai wei'
        m = yield 5
        print m
        d = yield 12
        print 'reach here '

def close(self):
        try:
                self.throw(GeneratorExit)
        except (GeneratorExit, StopIteration):
                pass
        else:
                raise RuntimeError("generator ignored GeneratorExit")

c = x()
c.next()
close(c)
c.next()

  

 

因此,当我们调用了close()方法后,再调用next()或是send(msg)的话会抛出一个异常:

Traceback (most recent call last):
  File "/home/evergreen/Codes/yidld.py", line 14, in <module>
    d = c.send('Fighting!')  #d 获取了yield 12 的参数值12
StopIteration

 

=============================

Python对协程的支持是通过generator实现的。

在generator中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值。

但是Python的yield不但可以返回一个值,它还可以接收调用者通过send发出的参数