列表推导与生成器表达式

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

>>> 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生成器与yield的更多相关文章

  1. python生成器中yield和send分析

    生成器 在python中生成器是指用代码实现迭代器的的功能本质还是迭代器,只不过是代码实现迭代器功能.在python中生成器是由函数实现的,通常我们在函数中加入yeild就可以实现生成器. 生成器中y ...

  2. Python生成器(yield)

    对于调用一个普通的Python函数,一般是从函数的第一行代码开始执行,结束于return语句.异常或者函数所有语句执行完毕.一旦函数将控制权交还给调用者,就意味着全部结束.函数中做的所有工作以及保存在 ...

  3. Python生成器以及yield语句

    生成器是一种暂缓求值的技术,它可以用来生成一系列的值,但不会一次性生成所有的值,而只在需要的时候才计算和生成一个值. 通过yield语句构建生成器 要得到一个生成器,我们需要定义一个函数,这个函数返回 ...

  4. Python 生成器与迭代器 yield 案例分析

    前几天刚开始看 Python ,后因为项目突然到来,导致Python的学习搁置了几天.然后今天看回Python 发现 Yield 这个忽然想不起是干嘛用的了(所以,好记性不如烂笔头.).然后只能 花点 ...

  5. Python中的生成器与yield

    对于python中的yield有些疑惑,然后在StackOverflow上看到了一篇回答,所以搬运过来了,英文好的直接看原文吧. 可迭代对象 当你创建一个列表的时候,你可以一个接一个地读取其中的项.一 ...

  6. 【Python】迭代器、生成器、yield单线程异步并发实现详解

    转自http://blog.itpub.net/29018063/viewspace-2079767 大家在学习python开发时可能经常对迭代器.生成器.yield关键字用法有所疑惑,在这篇文章将从 ...

  7. python迭代器与生成器及yield

    一.迭代器(itertor) 1.可迭代: 在Python中如果一个对象有__iter__()方法或__getitem__()方法,则称这个对象是可迭代的(iterable). 其中__iter__( ...

  8. Python中的yield和Generators(生成器)

    本文目的 解释yield关键字到底是什么,为什么它是有用的,以及如何来使用它. 协程与子例程 我们调用一个普通的Python函数时,一般是从函数的第一行代码开始执行,结束于return语句.异常或者函 ...

  9. python基础-8迭代器(iter)和生成器(yield)

    一 生成器 从Python2.2起,生成器提供了一种简洁的方式帮助返回列表元素的函数来完成简单和有效的代码. 它基于yield指令,允许停止函数并立即返回结果.此函数保存其执行上下文,如果需要,可立即 ...

随机推荐

  1. Google Breakpad part 1 : Getting Started With Windows Client

    准备 1.Python 2.Visual Studio 3.svn checkout http://google-breakpad.googlecode.com/svn/trunk/ source c ...

  2. C++对象的JSON序列化与反序列化探索续-复杂对象的序列化与反序列化

    本文是基本上一篇博文进行改进而成,上一篇请见: C++对象的JSON序列化与反序列化探索 此处就不多说了,直接上代码. 1. 序列化基类 #pragma once #include <strin ...

  3. 最新模仿ios版微信应用源码

    这个是刚刚从那个IOS教程网http://ios.662p.com分享来的,也是一个很不错的应用源码,仿微信基本功能.基于XMPP服务器的即时通信以及交友客户端. ----第一期代码的功能如下---- ...

  4. php版获取重定向后地址的代码分享

    如何获取重定向的地址呢?我们用php实现这样的功能,分享下我的代码,有需要的朋友参考下. 代码如下: <?php //取重定向的地址 class RedirectUrl{ //地址 var $u ...

  5. 通过 SuperObject 生成 json string

    (* { "name": "Henri Gourvest", /* this is a comment */ "vip": true, &q ...

  6. Spark Streaming揭秘 Day7 再探Job Scheduler

    Spark Streaming揭秘 Day7 再探Job Scheduler 今天,我们对Job Scheduler再进一步深入一下,对一些更加细节的源码进行分析. Job Scheduler启动 在 ...

  7. http://phantomjs.org/page-automation.html

    http://phantomjs.org/page-automation.html install brew curl -LsSf http://github.com/mxcl/homebrew/ta ...

  8. SQL Server 2008 的gis函数

    居然不知道sql有gis函数,孤陋寡闻了 https://msdn.microsoft.com/zh-cn/library/bb933904.aspx   STContains(geometry 数据 ...

  9. WPF常用数据绑定控件集合

    1.怎么用ListView控件把XML中的数据在界面上显示出来? <?xml version="1.0" encoding="utf-8" ?> & ...

  10. 【android-cocos2d-X iconv.h】在android下使用iconv

    (1) 下载文件 首先下载iconv文件  下载地址:http://download.csdn.net/detail/dingkun520wy/6703113 把解压后的iconv文件夹放到cocos ...