python生成器(转)

生成器是一种特殊的迭代器,内部支持了生成器协议,不需要明确定义__iter__()和next()方法。生成器通过生成器函数产生,生成器函数可以通过常规的def语句来定义,但是不用return返回,而是用yield一次返回一个结果。

一、yield和迭代器生成器

迭代器是非常高效的类型,无论是从时间复杂度,还是从空间复杂度。而实现迭代器的代码虽然简单,却也繁琐。为此,python定义了一个yield关键字,专门用来构造迭代器。yield有生成,产生的意思。

 

yield的功能和return非常类似,它们都只能在方法中使用。不同的是,包含yield语句的方法被称为生成器方法。当调用生成器方法时,会返回一个生成器对象。

 

例如,看下面的例子。

def MyGenerator():  
    yield 1  
  
gen = MyGenerator()  
print gen

 

输出结果为

<generator object MyGenerator at 0x0000000001D9DD80>

当调用生成器对象的next方法时,会执行生成器方法中的代码,直至遇到yield语句时,方法的执行过程会被挂起。同时,方法运行的上下文环境会被保存。而next方法的返回值就是yield关键字后面表达式的返回值。

 

例如,下面代码

print gen.next()

 执行结果为

1 

当我们继续调用next方法时,从上一次挂起的地方开始,继续执行后面的代码。直至遇到下一个yield语句。当方法执行完毕,依然没有遇到yield语句,抛出StopIteration异常。

 

 

例如

def MyGenerator():  
    yield 1  
    yield 'a'  
  
gen = MyGenerator()  
print gen.next()  
print gen.next()  
print gen.next() 

上面代码中第1次调用next方法,执行语句yield 1。第2次调用next方法,执行语句yield 'a'。第3次调用next方法时,在方法退出前都没有遇到yield语句,因此抛出StopIteration异常。

 

 

上面介绍的生成器方法的工作机理。在后面的博文中,会逐步介绍生成器方法的一些经典应用。

 

二、通过生成器函数构造序列对象的迭代器

事实上,一个序列对象的迭代器,依赖于一个整数序列的迭代器。看下面的代码

def MyGenerator(len):  
    start  = 0  
    while start < len:  
        yield start  
        start = start + 1  
  
  
gen = MyGenerator(3)  
print gen.next()  
print gen.next()  
print gen.next()  
print gen.next()  

 

当调用第1次next方法时, 会首先执行MyGenerator方法的第1行代码start = 0。然后进入循环。这里len的值通过参数传入为3。因此while的条件表达式为真。进入循环后,遇到yield语句,方法的执行过程被挂起。next方法的返回值为start的值,即0。

 

当调用第2次next方法时,接着上面的挂起点,往下执行start = start + 1语句,start的值变为1。接着又进入while循环的条件判断,start<len依然为真。因此,又执行yield语句。但是由于start值为1,故而这一次next方法返回的值为1。

第3次next方法的调用类似。

当调用第4次next方法时,while循环的条件判断start < len为假,while循环结束,MyGenerator方法调用也随之结束,抛出StopIteration异常。

 

输出结果

0  
1  
2  
Traceback (most recent call last):  
  File "test.py", line 21, in <module>  
    print gen.next()  
StopIteration 

有了上面的结果,重写序列对象的迭代器轻而易举。

def MyGenerator(sequence):  
    start  = 0  
    while start < len(sequence):  
        yield sequence[start]  
        start = start + 1  
  
  
gen = MyGenerator([1,2,3,'a','b','c'])  
  
for i in gen:  
    print i 

对比之前迭代器类的代码,我们可以认识到,yield关键字为构造迭代器提供了多大的方便。它使得代码长度缩减许多,同时也大大增强了可读性。

 

三、生成器对象的send方法

生成器对象是一个迭代器。但是它比迭代器对象多了一些方法,它们包括send方法,throw方法和close方法。这些方法,主要是用于外部与生成器对象的交互。本文先介绍send方法。

 

send方法有一个参数,该参数指定的是上一次被挂起的yield语句的返回值。这样说起来比较抽象,看下面的例子。

def MyGenerator():  
    value = (yield 1)  
    value = (yield value)  
  
  
gen = MyGenerator()  
print gen.next()  
print gen.send(2)  
print gen.send(3)

输出的结果如下

1  
2  
Traceback (most recent call last):  
  File "test.py", line 18, in <module>  
    print gen.send(3)  
StopIteration  

上面代码的运行过程如下。

当调用gen.next()方法时,Python首先会执行MyGenerator方法的yield 1语句。由于是一个yield语句,因此方法的执行过程被挂起,而next方法返回值为yield关键字后面表达式的值,即为1。

当调用gen.send(2)方法时,python首先恢复MyGenerator方法的运行环境。同时,将表达式(yield 1)的返回值定义为send方法参数的值,即为2。这样,接下来value=(yield 1)这一赋值语句会将value的值置为2。继续运行会遇到yield value语句。因此,MyGenerator方法再次被挂起。同时,send方法的返回值为yield关键字后面表达式的值,也即value的值,为2。

当调用send(3)方法时MyGenerator方法的运行环境。同时,将表达式(yield value)的返回值定义为send方法参数的值,即为3。这样,接下来value=(yield value)这一赋值语句会将value的值置为3。继续运行,MyGenerator方法执行完毕,故而抛出StopIteration异常。

 

总的来说,send方法和next方法唯一的区别是在执行send方法会首先把上一次挂起的yield语句的返回值通过参数设定,从而实现与生成器方法的交互。但是需要注意,在一个生成器对象没有执行next方法之前,由于没有yield语句被挂起,所以执行send方法会报错。例如

gen = MyGenerator()  
print gen.send(2) 

上面代码的输出为

Traceback (most recent call last):  
  File "test.py", line 16, in <module>  
    print gen.send(2)  
TypeError: can't send non-None value to a just-started generator

当然,下面的代码是可以接受的

gen = MyGenerator()  
print gen.send(None)

因为当send方法的参数为None时,它与next方法完全等价。但是注意,虽然上面的代码可以接受,但是不规范。所以,在调用send方法之前,还是先调用一次next方法为好。

 

四、生成器对象的throw方法

上边介绍的send方法,通过向生成器对象传递参数来实现与生成器对象的交互。本文介绍与生成器对象的另一种方式,即throw方法。它的实现手段是通过向生成器对象在上次被挂起处,抛出一个异常。之后会继续执行生成器对象中后面的语句,直至遇到下一个yield语句返回。如果在生成器对象方法执行完毕后,依然没有遇到yield语句,抛出StopIteration异常。

 

请看下面的例子

def myGenerator():  
    value = 1  
    while True:  
        yield value  
        value += 1  
  
  
gen = myGenerator()  
print gen.next()  
print gen.next()  
print gen.throw(Exception, "Method throw called!")  

输出结果为

1  
2  
Traceback (most recent call last):  
  File "test.txt", line 11, in <module>  
    print gen.throw(Exception, "Method throw called!")  
  File "test.txt", line 4, in myGenerator  
    yield value  
Exception: Method throw called!  

代码的最后一句向生成器对象抛出了一个异常。但是,在生成器对象的方法时没有处理该异常的代码,因此异常会被抛出到主方法。

 

 

下面的示例中,添加了处理异常的代码

def myGenerator():  
    value = 1  
    while True:  
        try:  
            yield value  
            value += 1  
        except:  
            value = 1  
  
  
gen = myGenerator()  
print gen.next()  
print gen.next()  
print gen.throw(Exception, "Method throw called!")  

在上面的代码中,加入了一个try-except语句块处理异常。当生成器方法收到异常后,会调到except语句块,将value置为1。因此,代码的输出如下。

1  
2  
1  
Exception RuntimeError: 'generator ignored GeneratorExit' in <generator object myGenerator at 0x00000000028BB900> ignored

上面输出中,第2个1是gen.throw方法的返回值。在执行完该方法后,生成器对象方法的while循环并没有结束,也即是说生成器方法的执行还没有结束。这个时候如果强制结束主程序,会抛出一个RuntimeError。也就是上面输出的第4行。要优雅地关闭主程序,需要用到生成器对象的close方法。

 

五、GeneratorExit异常

当一个生成器对象被销毁时,会抛出一个GeneratorExit异常。请看下面的代码。

def myGenerator():    
    try:  
        yield 1  
    except GeneratorExit:  
        print "myGenerator exited"  
  
  
  
gen = myGenerator()  
print gen.next() 

 

输出结果为

1  
myGenerator exited 

上面代码的运行逻辑如下: 当调用到gen.next()方法时,会执行生成器对象方法的yield语句。此后,主程序结束,系统会自动产生一个GeneratorExit异常,被生成器对象方法的Except语句块截获。

 

而GeneratorExit异常产生的时机,是在生成器对象被销毁之前。为了验证这点,请看下面的代码。

def myGenerator():    
    try:  
        yield 1  
        yield 2  
    except GeneratorExit:  
        print "myGenerator exited"  
  
  
  
gen = myGenerator()  
print gen.next()  
del gen  
print "Main caller exited" 

输出结果

1  
myGenerator exited  
Main caller exited 

值得一提的是,GeneratorExit异常只有在生成器对象被激活后,才有可能产生。更确切的说,需要至少调用一次生成器对象的next方法后,系统才会产生GeneratorExit异常。请看下面的代码。

def myGenerator():    
    try:  
        yield 1  
        yield 2  
    except GeneratorExit:  
        print "myGenerator exited"  
  
  
  
gen = myGenerator()  
del gen  
print "Main caller exited"  

其输出结果如下:

Main caller exited  

在上面的示例中,我们都显式地捕获了GeneratorExit异常。如果该异常没有被显式捕获,生成器对象也不会把该异常向主程序抛出。因为GeneratorExit异常定义的初衷,是方便开发者在生成器对象调用结束后定义一些收尾的工作,如释放资源等。

六、生成器对象的close方法

生成器对象的close方法会在生成器对象方法的挂起处抛出一个GeneratorExit异常。GeneratorExit异常产生后,系统会继续把生成器对象方法后续的代码执行完毕。参见下面的代码。

def myGenerator():    
    try:  
        yield 1  
        print "Statement after yield"  
    except GeneratorExit:  
        print "Generator error caught"  
  
    print "End of myGenerator"  
  
gen = myGenerator()  
print gen.next()  
gen.close()  
print "End of main caller"  

代码执行过程如下:

  • 当调用gen.next方法时,会激活生成器,直至遇到生成器方法的yield语句,返回值1。同时,生成器方法的执行被挂起。
  • 当调用gen,close方法时,恢复生成器方法的执行过程。系统在yield语句处抛出GeneratorExit异常,执行过程跳到except语句块。当except语句块处理完毕后,系统会继续往下执行,直至生成器方法执行结束。

代码的输出如下:

1  
Generator error caught  
End of myGenerator  
End of main caller

需要注意的是,GeneratorExit异常的产生意味着生成器对象的生命周期已经结束。因此,一旦产生了GeneratorExit异常,生成器方法后续执行的语句中,不能再有yield语句,否则会产生RuntimeError。请看下面的例子。

def myGenerator():    
    try:  
        yield 1  
        print "Statement after yield"  
    except GeneratorExit:  
        print "Generator error caught"  
  
    yield 3  
  
gen = myGenerator()  
print gen.next()  
gen.close()  
print "End of main caller"

输出结果为

1  
Generator error caught  
Traceback (most recent call last):  
  File "test.txt", line 12, in <module>  
    gen.close()  
RuntimeError: generator ignored GeneratorExit

注意,由于RuntimError会向主方法抛出,因此主方法最后的print语句没有执行。

 

 

有了上面的知识,我们就可以理解为什么下面的代码会抛出RuntimError错误了。

def myGenerator():    
    value = 1    
    while True:    
        try:    
            yield value    
            value += 1    
        except:    
            value = 1    
    
    
gen = myGenerator()    
print gen.next()    
print gen.next()    
print gen.throw(Exception, "Method throw called!") 

上面代码中,当主程序结束前,系统产生GeneratorExit异常,被生成器对象方法的except语句捕获,但是此时while语句还没有退出,因此后面还会执行“yield value”这一语句,从而发生RuntimeError。要避免这个错误非常简单,请看下面的代码。

def myGenerator():    
    value = 1    
    while True:    
        try:    
            yield value    
            value += 1    
        except Exception:    
            value = 1    
    
    
gen = myGenerator()    
print gen.next()    
print gen.next()    
print gen.throw(Exception, "Method throw called!") 

代码第7行的except语句声明只捕获Exception异常对象。这样,当系统产生GeneratorExit异常后,不再被except语句捕获,继续向外抛出,从而跳出了生成器对象方法的while语句。

 

 

这里再简单说一句,GeneratorExit异常继承自BaseException类。BaseException类与Exception类不同。一般情况下,BaseException类是所有内建异常类的基类,而Exception类是所有用户定义的异常类的基类。

 

 

转自:http://blog.csdn.net/hedan2013/article/details/72811117

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值