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