Python中的生成器和yield详细介绍


Posted in Python onJanuary 09, 2015

列表推导与生成器表达式

当我们创建了一个列表的时候,就创建了一个可以迭代的对象:

>>> squares=[n*n for n in range(3)]

>>> for i in squares:

 print i

 

0

1

4

这种创建列表的操作很常见,称为列表推导。但是像列表这样的迭代器,比如str、file等,虽然用起来很方便,但有一点,它们是储存在内存中的,如果值很大,会很麻烦。

而生成器表达式不同,它执行的计算与列表包含相同,但会迭代的生成结果。它的语法与列表推导一样,只是要用小括号来代替中括号:

>>> squares=(n*n for n in range(3))

>>> for i in squares:

 print i

 

0

1

4

生成器表达式不会创建序列形式的对象,不会把所有的值都读取到内存中,而是会创建一个通过迭代并按照需求生成值的生成器对象(Generator)。

那么,还有没有其它方法来产生生成器呢?

例子:斐波那契数列

例如有个需求,要生成斐波那契数列的前10位,我们可以这样写:

def fib(n):

    result=[]

    a=1

    b=1

    result.append(a)

    for i in range(n-1):

        a,b=b,a+b

        result.append(a)

    return result

if __name__=='__main__':

    print fib(10)

数字很少时,函数运行良好,但数字很多时,问题就来了,显然生成一个几千几万长度的列表并不是一个很好的主意。

这样,需求就变成了:写一个可以生成可迭代对象的函数,或者说,不要让函数一次返回全部的值,而是一次返回一个值。

这好像与我们的常识相违背,当我们调用一个普通的Python函数时,一般是从函数的第一行代码开始执行,结束于return语句、异常或者函数结束(可以看作隐式的返回None):

def fib(n):

    a=1

    b=1

    for i in range(n-1):

        a,b=b,a+b

        return a

if __name__=='__main__':

    print fib(10)

>>> 

1    #返回第一个值时就卡住了

函数一旦将控制权交还给调用者,就意味着全部结束。函数中做的所有工作以及保存在局部变量中的数据都将丢失。再次调用这个函数时,一切都将从头创建。函数只有一次返回结果的机会,因而必须一次返回所有的结果。通常我们都这么认为的。但是,如果它们并非如此呢?请看神奇的yield:
def fib(n):

    a=1

    yield a

    b=1

    for i in range(n-1):

        a,b=b,a+b

        yield a

if __name__=='__main__':

    for i in fib(10):

        print i

>>> 

1

1

2

3

5

8

13

21

34

生成器Generator

python中生成器的定义很简单,使用了yield关键字的函数就可以称之为生成器,它生成一个值的序列:

def countdown(n):

    while n>0:

        yield n

        n-=1

if __name__=='__main__':

    for i in countdown(10):

        print i

生成器函数返回生成器。要注意的是生成器就是一类特殊的迭代器。作为一个迭代器,生成器必须要定义一些方法,其中一个就是__next__()。如同迭代器一样,我们可以使用next()函数(Python3是__next__() )来获取下一个值:
>>> c=countdown(10)

>>> c.next()

10

>>> c.next()

9

每当生成器被调用的时候,它会返回一个值给调用者。在生成器内部使用yield来完成这个动作。为了记住yield到底干了什么,最简单的方法是把它当作专门给生成器函数用的特殊的return。调用next()时,生成器函数不断的执行语句,直至遇到yield为止,此时生成器函数的”状态”会被冻结,所有的变量的值会被保留下来,下一行要执行的代码的位置也会被记录,直到再次调用next()继续执行yield之后的语句。

next()不能无限执行,当迭代结束时,会抛出StopIteration异常。迭代未结束时,如果你想结束生成器,可以使用close()方法。

>>> c.next()

1

>>> c.next()

StopIteration

>>> c=countdown(10)

>>> c.next()

10

>>> c.close()

>>> c.next()

StopIteration

协程与yield表达式

yield语句还有更给力的功能,作为一个语句出现在赋值运算符的右边,接受一个值,或同时生成一个值并接受一个值。

def recv():

    print 'Ready'

    while True:

        n=yield

        print 'Go %s'%n

>>> c=recv()

>>> c.next()

Ready

>>> c.send(1)

Go 1

>>> c.send(2)

Go 2

以这种方式使用yield语句的函数称为协程。在这个例子中,对于next()的初始调用是必不可少的,这样协程才能执行可通向第一个yield表达式的语句。在这里协程会挂起,等待相关生成器对象send()方法给它发送一个值。传递给send()的值由协程中的yield表达式返回。

协程的运行一般是无限期的,使用方法close()可以显式的关闭它。

如果yield表达式中提供了值,协程可以使用yield语句同时接收和发出返回值。

def split_line():

    print 'ready to split'

    result=None

    while True:

        line=yield result

        result=line.split()

>>> s=split_line()

>>> s.next()

ready to split

>>> s.send('1 2 3')

['1', '2', '3']

>>> s.send('a b c')

['a', 'b', 'c']

注意:理解这个例子中的先后顺序非常重要。首个next()方法让协程执行到yield result,这将返回result的值None。在接下来的send()调用中,接收到的值被放到line中并拆分到result中。send()方法的返回值就是下一条yield语句的值。也就是说,send()方法可以将一个值传递给yield表达式,但是其返回值来自下一个yield表达式,而不是接收send()传递的值的yield表达式。

如果你想用send()方法来开启协程的执行,必须先send一个None值,因为这时候是没有yield语句来接受值的,否则就会抛出异常。

>>> s=split_line()

>>> s.send('1 2 3')

TypeError: can't send non-None value to a just-started generator

>>> s=split_line()

>>> s.send(None)

ready to split

使用生成器与协程

乍看之下,如何使用生成器和协程解决实际问题似乎并不明显。但在解决系统、网络和分布式计算方面的某些问题时,生成器和协程特别有用。实际上,yield已经成为Python最强大的关键字之一。

比如,要建立一个处理文件的管道:

import os,sys

def default_next(func):

    def start(*args,**kwargs):

        f=func(*args,**kwargs)

        f.next()

        return f

    return start

@default_next

def find_files(target):

    topdir=yield

    while True:

        for path,dirname,filelist in os.walk(topdir):

            for filename in filelist:

                target.send(os.path.join(path,filename))
@default_next

def opener(target):

    while True:

        name=yield

        f=open(name)

        target.send(f)

    

@default_next

def catch(target):

    while True:

        f=yield

        for line in f:

            target.send(line)

            

@default_next

def printer():

    while True:

        line=yield

        print line

然后将这些协程连接起来,就可以创建一个数据流处理管道了:
finder=find_files(opener(catch(printer())))

finder.send(toppath)

程序的执行完全由将数据发送到第一个协程find_files()中来驱动,协程管道会永远保持活动状态,直到它显式的调用close()。

总之,生成器的功能非常强大。协程可以用于实现某种形式的并发。在某些类型的应用程序中,可以用一个任务调度器和一些生成器或协程实现协作式用户空间多线程,即greenlet。yield的威力将在协程,协同式多任务处理(cooperative multitasking),以及异步IO中得到真正的体现。

Python 相关文章推荐
Python 网络编程起步(Socket发送消息)
Sep 06 Python
跟老齐学Python之Python文档
Oct 10 Python
深入讲解Java编程中类的生命周期
Feb 05 Python
MAC中PyCharm设置python3解释器
Dec 15 Python
Python3.8中使用f-strings调试
May 22 Python
Flask框架实现的前端RSA加密与后端Python解密功能详解
Aug 13 Python
使用python获取邮箱邮件的设置方法
Sep 20 Python
Python编译成.so文件进行加密后调用的实现
Dec 23 Python
python各层级目录下import方法代码实例
Jan 20 Python
Idea安装python显示无SDK问题解决方案
Aug 12 Python
python openCV自制绘画板
Oct 27 Python
Pandas数据分析的一些常用小技巧
Feb 07 Python
Python中实现对list做减法操作介绍
Jan 09 #Python
python base64 decode incorrect padding错误解决方法
Jan 08 #Python
Python中字符编码简介、方法及使用建议
Jan 08 #Python
Python实现一个简单的MySQL类
Jan 07 #Python
python实现多线程暴力破解登陆路由器功能代码分享
Jan 04 #Python
Python中对列表排序实例
Jan 04 #Python
Python实现爬取知乎神回复简单爬虫代码分享
Jan 04 #Python
You might like
PHP中使用json数据格式定义字面量对象的方法
2014/08/20 PHP
php数组操作之键名比较与差集、交集赋值的方法
2014/11/10 PHP
PHP中str_split()函数的用法讲解
2019/04/11 PHP
thinkphp3.2框架中where条件查询用法总结
2019/08/13 PHP
php探针不显示内存解决方法
2019/09/17 PHP
基于laravel where的高级使用方法
2019/10/10 PHP
javascript import css实例代码
2008/07/18 Javascript
jquery1.4.2 for Visual studio 2010 模板文件
2010/07/14 Javascript
用JQuery在网页中实现分隔条功能的代码
2012/08/09 Javascript
javascript右下角弹层及自动隐藏(自己编写)
2013/11/20 Javascript
JS获取下拉列表所选中的TEXT和Value的实现代码
2014/01/11 Javascript
输入法的回车与消息发送快捷键回车的冲突解决方法
2016/08/09 Javascript
浅谈javascript中的 “ && ” 和 “ || ”
2017/02/02 Javascript
基于JS实现移动端向左滑动出现删除按钮功能
2017/02/22 Javascript
微信小程序页面滑动屏幕加载数据效果
2020/11/16 Javascript
vue-cli结合Element-ui基于cropper.js封装vue实现图片裁剪组件功能
2018/03/01 Javascript
JavaScript中变量提升与函数提升经典实例分析
2018/07/26 Javascript
vue 的点击事件获取当前点击的元素方法
2018/09/15 Javascript
JavaScript中跨域问题的深入理解
2021/03/04 Javascript
python获取文件版本信息、公司名和产品名的方法
2014/10/05 Python
Python Scapy随心所欲研究TCP协议栈
2018/11/20 Python
美国祛痘、抗衰老药妆品牌:Murad
2016/08/27 全球购物
Dockers鞋官网:Dockers Shoes
2018/11/13 全球购物
C语言50道问题
2014/10/23 面试题
终端业务员岗位职责
2013/11/27 职场文书
优秀企业获奖感言
2014/02/01 职场文书
小班上学期评语
2014/05/05 职场文书
企业仓管员岗位职责
2014/06/15 职场文书
政风行风建设责任书
2014/07/23 职场文书
中国文明网向国旗敬礼活动精彩寄语2014
2014/09/27 职场文书
2014基建处领导班子“四风”对照检查材料思想汇报
2014/10/04 职场文书
毕业生就业推荐表自我评价
2015/03/02 职场文书
学生犯错保证书
2015/05/09 职场文书
委托收款证明
2015/06/23 职场文书
2016年教师节贺卡寄语
2015/12/04 职场文书
2016年“12.4”法制宣传日活动总结
2016/04/01 职场文书