python 生成器小结

作者: PyChina | 来源:发表于2016-09-09 17:52 被阅读537次

    作者:邵正将 来源:PytLab

    在python中生成器可以很方便的实现迭代协议。生成器通过生成器函数产生,生成器函数可以通过常规的def语句来定义,但是不用return返回,而是用yield一次返回一个结果,在每个结果之间挂起和继续它们的状态,来自动实现迭代协议。也就是说,yield是一个语法糖,内部实现支持了迭代器协议。

    生成器的强大之一就在于他提供了协同程序的概念,协同程序是可以运行的独立函数调用,可以暂停或者挂起,并从程序离开的地方继续或者重新开始。同时调用者也可以向程序传入额外的数据或者异常等,传入完毕后仍能在上次暂停的地方继续执行。

    简单的生成器特性

    我先以上一篇的列表的例子实现一个生成器:

    def ListGenerator(data):
        for i in data:
            yield i
    

    之所以我把函数写的像类一样,是因为含有yield的函数并不是一般的函数,它更像一个类,因为它返回一个对象,这个对象就叫生成器。
    现在我生成一个生成器对象:

    In [1]: from list_iter import *
    
    In [2]: a = ListGenerator([1,2,3,4,5])
    
    In [3]: a
    Out[3]: <generator object ListGenerator at 0x035DA6C0>
    

    这样我们可以像使用迭代器一样去使用生成器对象:

    In [5]: [i for i in a]
    Out[5]: [1, 2, 3, 4, 5]
    

    生成器的加强特性

    在python2.5中一些加强特性加入到生成器中,让生成器更加的强大,所以除了__next__()可以获取下一个生成的值,用户可以将值回送个生成器(send()),在生成器抛出异常,以及要求生成器退出(close()

    send()方法

    由于双向的动作涉及到一个叫send()的代码来向生成器发送值,因此现在yield必须是一个表达式,因为函数会在执行yield之后暂停,等他回来的时候必须要有一个对象接手值,因此要写成a = yield b这样。
    举个简单的例子:

    def ListGenerator(data):
        for i in data:
            receive = (yield i)
            if receive:
                print("receive {}.".format(receive))
            else:
                print("receive nothing.")
    

    执行的结果:

    In [47]: a = ListGenerator([1,2,3])
    
    In [48]: a.__next__()
    Out[48]: 1
    
    In [49]: a.__next__()
    receive nothing.
    Out[49]: 2
    
    In [50]: a.send(1.3)
    receive 1.3.
    Out[50]: 3
    

    简单分析下上面这段程序就很清楚了:

    1. 创建生成器对象
      首先使用函数ListGenerator,python看到函数里有yield关键字就知道这个函数不一般要返回一个生成器对象,并赋值给变量a

    2. 第一次调用__next__()
      然后a就是个迭代器对象,它实现了迭代协议(即实现了__iter__()__next__()方法,其中__iter__()返回生成器自身),我们调用__next__()函数函数体就开始执行,当执行到

      receive = (yield i)
      

      python看到了yield就把i返回并暂停函数执行。这也就是为什么第一次调用__next__()时先只输出了1。

    3. 第二次调用__next__()
      当再次调用__next__()函数时候,函数便会在上次停止的地方继续执行,上次执行到了哪里?yield执行之后是一个赋值操作,要把一个值赋给receive,但是这时候我们没有给生成器发送值,因此python默认将这个值赋值为None。然后进行下面的判断操作,由于receive的值为None所以直接打印receive nothing.,然后继续执行,这时i变为2并在再次遇到yield生成器返回2并暂停。

    4. 调用send()方法
      这是在生成器恢复执行之前,我调用了生成器的send()函数,向生成器传入了一个值1.3,传入之后生成器恢复函数的执行,将我传入的值赋值给receive然后接着向下进行判断这是由于receive的值是1.3了因此输出receive 1.3.。然后接着第三轮迭代输出3.

    throw方法

    throw主要是向生成器发送异常,我将上面的代码进行了修改,是生成器能够捕获异常:

    def ListGenerator(data):
        for i in data:
            try:
                receive = (yield i)
                if receive:
                    print("receive {}.".format(receive))
                else:
                    print("receive nothing.")
            except ValueError:
                print("receive a ValueError.")
    

    在shell中执行:

    In [1]: from list_iter import *
    
    In [2]: a = ListGenerator([1,2,3,4])
    
    In [3]: a.__next__()
    Out[3]: 1
    
    In [4]: a.throw(ValueError)
    receive a ValueError.
    Out[4]: 2
    

    当第一次调用__next__()的时候,执行到yield返回1并暂停,第二次我传入了一个VauleError异常,生成器继续执行函数,由于接收到一个异常,并被下面的except ValueError捕获,这时便输出了recieve a ValueError.然后接着执行到yield返回2。

    close()方法

    当一个生成器是一个永远执行的时候(while True的时候),我们就用到了close()来终止它。

    通过生成器,可以快速创建一个可迭代对象。

    上一篇中提到过,我们可以通过将可迭代对象中的__iter__()方法返回一个迭代器对象来实现多次重复迭代。有了生成器,我们可以让可迭代对象的__iter__()方法直接返回一个生成器,也就是在__iter__()中使用yield关键字,这样创建迭代对象就很方便。我之前处理VASP文件的库VASPy中就是这样使用来从大文件中获取数据的,代码不贴上来了,直接放个链接吧: <span class="fa fa-github"></span> VASPy/atomco.py at master · PytLab/VASPy

    今年第六届大会PyConChina2016,由PyChina.org发起,CPyUG/TopGeek 等社区协办,将在2016年9月10日(上海)9月23日(深圳)10月15日(北京)地举办的针对Python开发者所举办的最盛大和权威的Python相关技术会议,由PyChina社区主办,致力于推动各类Python相关的技术在互联网、企业应用等领域的研发和应用。

    您可以点击此处
    了解更多详情,或者扫描下图二维码:

    相关文章

      网友评论

        本文标题: python 生成器小结

        本文链接:https://www.haomeiwen.com/subject/blaeettx.html