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语言的12个基础知识点小结
Jul 10 Python
深入浅析python继承问题
May 29 Python
python中函数传参详解
Jul 03 Python
Python上下文管理器和with块详解
Sep 09 Python
如何利用python查找电脑文件
Apr 27 Python
Django  ORM 练习题及答案
Jul 19 Python
tensorflow 实现自定义layer并添加到计算图中
Feb 04 Python
k-means 聚类算法与Python实现代码
Jun 01 Python
Python Django路径配置实现过程解析
Nov 05 Python
Python+Opencv实现把图片、视频互转的示例
Dec 17 Python
python 逆向爬虫正确调用 JAR 加密逻辑
Jan 12 Python
python模板入门教程之flask Jinja
Apr 11 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
利用static实现表格的颜色隔行显示
2006/10/09 PHP
PHP 线程安全与非线程安全版本的区别深入解析
2013/08/06 PHP
PHP图像识别技术原理与实现
2016/10/27 PHP
PHP进程通信基础之信号量与共享内存通信
2017/02/19 PHP
php实现页面纯静态的实例代码
2017/06/21 PHP
一些常用的JS功能函数(2009-06-04更新)
2009/06/04 Javascript
JS类的封装及实现代码
2009/12/02 Javascript
Jjcarousellite 实现图片列表滚动的简单实例
2013/11/29 Javascript
js 上下左右键控制焦点(示例代码)
2013/12/14 Javascript
初步使用bootstrap快速创建页面
2016/03/03 Javascript
浅谈json取值(对象和数组)
2016/06/24 Javascript
浅析vue-router原理
2018/10/19 Javascript
基于jquery实现九宫格拼图小游戏
2018/11/30 jQuery
JS实现提示效果弹出及延迟隐藏的功能
2019/08/26 Javascript
jquery实现进度条状态展示
2020/03/26 jQuery
vue项目配置同一局域网可使用ip访问的操作
2020/10/23 Javascript
Vue 防止短时间内连续点击后多次触发请求的操作
2020/11/11 Javascript
详解Python中的__init__和__new__
2014/03/12 Python
Python爬虫实例爬取网站搞笑段子
2017/11/08 Python
Python+selenium实现截图图片并保存截取的图片
2018/01/05 Python
Python使用 Beanstalkd 做异步任务处理的方法
2018/04/24 Python
Python子进程subpocess原理及用法解析
2020/07/16 Python
python中的垃圾回收(GC)机制
2020/09/21 Python
html+css3实现的登录界面
2020/12/09 HTML / CSS
HTML5 实现图片上传预处理功能
2020/02/06 HTML / CSS
韩国休闲女装品牌网站:ANAIS
2016/08/24 全球购物
火山咖啡:Volcanica Coffee
2019/10/29 全球购物
可靠的数据流传输TCP
2016/03/15 面试题
最新创业融资计划书
2014/01/19 职场文书
端午节粽子促销活动方案
2014/02/02 职场文书
计算机科学与技术专业求职信
2014/09/03 职场文书
离婚纠纷代理词
2015/05/23 职场文书
2016年班主任培训心得体会
2016/01/07 职场文书
python小程序之飘落的银杏
2021/04/17 Python
浅谈Python中的函数(def)及参数传递操作
2021/05/25 Python
详解PHP用mb_string处理windows中文字符
2021/05/26 PHP