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 相关文章推荐
在Ubuntu系统下安装使用Python的GUI工具wxPython
Feb 18 Python
详解常用查找数据结构及算法(Python实现)
Dec 09 Python
Python字符串处理实例详解
May 18 Python
Python批量更改文件名的实现方法
Oct 29 Python
Python使用Matplotlib实现Logos设计代码
Dec 25 Python
python调用OpenCV实现人脸识别功能
May 25 Python
浅谈Python 递归算法指归
Aug 22 Python
简单了解python元组tuple相关原理
Dec 02 Python
Pycharm 安装 idea VIM插件的图文教程详解
Feb 21 Python
Python3之外部文件调用Django程序操作model等文件实现方式
Apr 07 Python
python将下载到本地m3u8视频合成MP4的代码详解
Nov 24 Python
教你怎么用PyCharm为同一服务器配置多个python解释器
May 31 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
长波有什么东西
2021/03/01 无线电
PHP新手上路(八)
2006/10/09 PHP
PHP函数常用用法小结
2010/02/08 PHP
PHP防止注入攻击实例分析
2014/11/03 PHP
PHP使用PDO连接ACCESS数据库
2015/03/05 PHP
高质量PHP代码的50个实用技巧必备(下)
2016/01/22 PHP
ThinkPHP中session函数详解
2016/09/14 PHP
php查询及多条件查询
2017/02/26 PHP
PHP实现针对日期,月数,天数,周数,小时,分,秒等的加减运算示例【基于strtotime】
2017/04/19 PHP
打开超链需要“确认”对话框的方法
2007/03/08 Javascript
IFrame跨域高度自适应实现代码
2012/08/16 Javascript
JavaScript中的数值范围介绍
2014/12/29 Javascript
Bootstrap中CSS的使用方法
2016/02/17 Javascript
动态加载JavaScript文件的两种方法
2016/04/22 Javascript
AngularJS基础 ng-non-bindable 指令详细介绍
2016/08/02 Javascript
node.js实现博客小爬虫的实例代码
2016/10/08 Javascript
AngularJS中一般函数参数传递用法分析
2016/11/22 Javascript
Vue2.0实现购物车功能
2017/06/05 Javascript
iscroll-probe实现下拉刷新和下拉加载效果
2017/06/28 Javascript
浅谈函数调用的不同方式,以及this的指向
2017/09/17 Javascript
canvas绘制爱心的几种方法总结(推荐)
2017/10/31 Javascript
react build 后打包发布总结
2018/08/24 Javascript
微信小程序如何调用图片接口API并居中显示
2019/06/29 Javascript
javascript中的相等操作符(==与===区别)
2019/12/21 Javascript
如何管理Vue中的缓存页面
2021/02/06 Vue.js
Python修改MP3文件的方法
2015/06/15 Python
Python实现决策树C4.5算法的示例
2018/05/30 Python
Python sklearn库实现PCA教程(以鸢尾花分类为例)
2020/02/24 Python
Django实现将views.py中的数据传递到前端html页面,并展示
2020/03/16 Python
Ryderwear澳洲官网:澳大利亚高端健身训练装备品牌
2018/09/18 全球购物
Farfetch中文官网:奢侈品牌时尚购物平台
2020/03/15 全球购物
《鸿门宴》教学反思
2014/04/22 职场文书
迎新春趣味活动方案
2014/08/24 职场文书
2014年测量员工作总结
2014/12/12 职场文书
成人成长感言如何写?
2019/08/16 职场文书
教你如何用cmd快速登录服务器
2022/06/10 Servers