python 生成器小结

作者:邵正将 来源: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相关的技术在互联网、企业应用等领域的研发和应用。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容