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

时间: 2024-09-30 19:44:54

python生成器(转)的相关文章

Python 生成器&迭代器

Python 生成器 带有 yield 的函数在 Python 中被称之为 generator(生成器),用斐波那契数列: def fab(max):     n, a, b = 0, 0, 1     while n < max:         yield b         a, b = b, a + b         n = n + 1 执行: 1 2 3 4 5 6 7 8 9 >>> for n in fab(5):     print n 1 1 2 3 5 简单地

对python生成器特性使用的好例子

1.对序列进行分组的函数(摘自web.py源码utils.py文件中) 1 def group(seq, size): 2 """ 3 Returns an iterator over a series of lists of length size from iterable. 4 5 >>> list(group([1,2,3,4], 2)) 6 [[1, 2], [3, 4]] 7 >>> list(group([1,2,3,4,5]

python生成器(笔记)

Python生成器有些难以用语言表达其概念,所以在这里用几段代码来解释~ 生成器:任何包含yield语句的函数称为生成器; 生成器是一种普通的函数语法定义的迭代器. def test2():     print 9     print 8     yield 7 test2() 上述代码什么都不返回,因为代码碰到了yield函数暂停(或冻结)了,这个暂停同时还影响了yield以上的两个print. def test2():     print 9     print 8     yield 7

Python生成器、迭代器、装饰器

Python迭代器 迭代器是访问集合内元素的一种方式.迭代器对象从集合的第一个元素开始访问,直到所有的元素都被访问一遍后结束. 迭代器不能回退,只能往前进行迭代.这并不是什么很大的缺点,因为人们几乎不需要在迭代途中进行回退操作. 常用的迭代方法有 .next()方法 for..in..方法 迭代器通俗的理解就是遍历集合内的所有元素 python生成器 这里先说简单的使用,然后再说自己创建生成器 range:生成一个list range(1,5)结果为:[1,2,3,4] xrange:生成一个x

4.Python 生成器yield

常用方法: next    获取下一个值 send    发送值到生成器 throw  发送异常到生成器 python生成器模拟线程并发:

Python 生成器和推导式

一.Python生成器和生成器函数1.生成器和生成器函数的概念    1.生成器的本质是迭代器    2.函数中包含yield,就是生成器函数 2.生成器函数的写法    def func():        a =10        yield 20    gen = func()  #没有执行,而是生成一个生成器    普通函数和生成器函数的不同    1.普通函数名()表示函数的的执行    2.生成器函数名()不是函数的执行,而是生成一个生成器 yield和return的不同    1.

Python 生成器以及应用

一.定义 可以理解为一种数据类型,这种数据类型自动实现了迭代器协议(其他的数据类型需要调用自己内置的__iter__方法),所以生成器就是可迭代对象 二.生成器的两种形式(Python有两种不同的方式提供生成器) 1.生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果.yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行 yield的功能: 1 把函数的结果做生迭代器(以一种优雅的方式封装好__iter__,__next__

python 生成器理解

通过列表生成式,我们可以直接创建一个列表.但是,受到内存限制,列表容量肯定是有限的.而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了. 所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间.在Python中,这种一边循环一边计算的机制,称为生成器(Generator). 简单生成器 要创建一个generator,有很

Python生成器(yield)

对于调用一个普通的Python函数,一般是从函数的第一行代码开始执行,结束于return语句.异常或者函数所有语句执行完毕.一旦函数将控制权交还给调用者,就意味着全部结束.函数中做的所有工作以及保存在局部变量中的数据都将丢失.再次调用这个函数时,一切都将从头创建.Python是通过生成器来实现类似于协同程序的概念:生成器可以暂时挂起函数,并保留函数的局部变量等数据,然后在再次调用它的时候,从上次暂停的位置继续执行下去. 提高你的 Python:解释 yield 和 Generators(生成器)

python 生成器和迭代器

迭代器协议 1.迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个Stoplteration异常,以终止迭代(只能往后走不能往前退) 2.可迭代对象:实现了迭代器协议的对象(如何实现:对象内部定义一个__iter__()方法) 3.协议是一种约定,可迭代对象实现了迭代器协议,python的内部工具(如for循环,sum,min,max函数等)使用迭代器协议访问对象. 迭代:重复做一件事 iterable(可迭代)对象 支持每次返回自己所包含的一个成员的