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 相关文章推荐
50行代码实现贪吃蛇(具体思路及代码)
Apr 27 Python
在Python中处理XML的教程
Apr 29 Python
Python中用于计算对数的log()方法
May 15 Python
Python提取Linux内核源代码的目录结构实现方法
Jun 24 Python
利用python3随机生成中文字符的实现方法
Nov 24 Python
python如何重载模块实例解析
Jan 25 Python
Python3用tkinter和PIL实现看图工具
Jun 21 Python
python开发之anaconda以及win7下安装gensim的方法
Jul 05 Python
TensorFlow2.X使用图片制作简单的数据集训练模型
Apr 08 Python
将pycharm配置为matlab或者spyder的用法说明
Jun 08 Python
django中ImageField的使用详解
Dec 21 Python
python之json文件转xml文件案例讲解
Aug 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
星际争霸秘籍
2020/03/04 星际争霸
php中文本操作的类
2007/03/17 PHP
php反射类ReflectionClass用法分析
2016/05/12 PHP
推荐:极酷右键菜单
2006/11/29 Javascript
修改发贴的编辑功能
2007/03/07 Javascript
JQuery获取当前屏幕的高度宽度的实现代码
2011/07/12 Javascript
js字母大小写转换实现方法总结
2013/11/13 Javascript
jquery append()方法与html()方法的区别及使用介绍
2014/08/01 Javascript
jquery实现类似淘宝星星评分功能实例
2014/09/12 Javascript
封装了jQuery的Ajax请求全局配置
2015/02/05 Javascript
在Ubuntu系统上安装Node.JS的教程
2015/10/15 Javascript
javascript中利用柯里化函数实现bind方法
2016/04/29 Javascript
js实现图片淡入淡出切换简易效果
2016/08/22 Javascript
基于javascript的Form表单验证
2016/12/29 Javascript
详解利用 Vue.js 实现前后端分离的RBAC角色权限管理
2017/09/15 Javascript
Angular实现较为复杂的表格过滤,删除功能示例
2017/12/23 Javascript
Angular CLI在Angular项目中如何使用scss详解
2018/04/10 Javascript
vue2中,根据list的id进入对应的详情页并修改title方法
2018/08/24 Javascript
浅谈Node框架接入ELK实践总结
2019/02/22 Javascript
在vue项目中利用popstate处理页面返回的操作介绍
2020/08/06 Javascript
[42:20]Secret vs Liquid 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/17 DOTA
Python中内置数据类型list,tuple,dict,set的区别和用法
2015/12/14 Python
英国领先的在线高尔夫商店:Scottsdale Golf
2019/08/26 全球购物
业务经理的岗位职责
2013/11/16 职场文书
毕业生简单求职信
2013/11/19 职场文书
开办加工厂创业计划书
2014/01/03 职场文书
商场端午节活动方案
2014/01/29 职场文书
汽车维修工岗位职责
2014/02/12 职场文书
小学竞选班干部演讲稿
2014/08/20 职场文书
党员四风自我剖析材料
2014/10/07 职场文书
监察建议书
2015/02/04 职场文书
2015年导购员工作总结
2015/04/25 职场文书
酒桌上的开场白
2015/06/01 职场文书
改进工作作风心得体会
2016/01/23 职场文书
Mysql服务添加 iptables防火墙策略的方案
2021/04/29 MySQL
《进击的巨人》新联动CM 兵长强势出击兽巨人
2022/04/05 日漫