python中yield使用
列表推导与生成器表达式
当我们创建了一个列表的时候,就创建了一个可以迭代的对象:
>>> 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)。那么,还有没有其它方法来产生生成器呢?
例子:斐波那契数列
0是第0项,不是第一项。
这个数列从第二项开始,每一项都等于前两项之和。
例如有个需求,要生成斐波那契数列的前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 #返回第一个值时就卡住了 |
函数一旦将控制权交还给调用者,就意味着全部结束。函数中做的所有工作以及保存在局部变量中的数据都将丢失。再次调用这个函数时,一切都将从头创建。函数只有一次返回结果的机会,因而必须一次返回所有的结果。通常我们都这么认为的。但是,并非如此:
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表达式
协程的原理很简单,打个比方就能讲明白了:假设有十个人去食堂打饭,这个食堂比较穷,只有一个打饭窗口和一个打饭阿姨,那么打饭就只能一个一个排队进行。这十个人胃口很大,每个人都要点5个菜,但这十个人都喜欢犹豫不决,点菜的时候每点一个菜后再想下一个菜点什么,因此后面的人等得很着急呀。
这样一直站着也不是个事儿,所以打菜的阿姨看到某人犹豫5秒后就会吼一声,让他排到队伍末尾,让别人先打菜,等轮到他的时候他也差不多想好吃什么了。这确实是个不错的方法,但也有一个缺点,那就是打菜的阿姨会等每个人5秒钟,如果那个人在5秒内没有做出决定吃啥,其实这5秒就浪费了。一个人点一个菜就是浪费5秒,十个人每个人点5个菜可就浪费的多啦「菜都凉了要」。那怎么办呢?阿姨又发话了:大家都是学生,学生就要自觉,我以后也不主动让你们排到末尾了,如果你们觉得自己会犹豫不决,就主动点直接点一个菜就站后面去,等下次排到的时候也差不多想好吃啥了。这个方法果然有效,大家点了菜后想的第一件事情不是下一个菜吃什么,而是自己会不会犹豫,如果会犹豫那直接排到队伍后面去,如果不会就接着点菜。这样一来整个队伍的效率自然就高了。
这个例子里,排队阿姨的那声吼就是我们的 CPU 中断,用于切换上下文。每个打饭的学生就是一个 task。而每个人决定自己要不要让出窗口的这种行为,其实就是我们协程的核心思想。
协程就是一种可以在代码的各种预定义位置暂停和恢复执行的函数,它避免了无意义的调度,由此提高代码性能。而子程序是一种特殊的协同程序,它只有单一入口,通过回调来完成执行。Python 的协程「现有的以生成器为基础的协程和新提出的协程」不是一般意义上的协程,因为在执行暂停时它们只能将控制权转给调用者,而不是像常见的那样将控制权转给别的协程。辅之以事件循环,协程可用于异步处理,尤其是在 I / O 中。
先让我们来看一下进程和线程。
未来让计算机能够同时处理多个任务,操作系统有了进程的概念,而且在进程内部,基本可以认为当前系统只有一个进程在运行,操作系统对此作了非常好的封装。进程间的切换是有操作系统来完成的。进程有一个问题,就是进程间切换耗费计算机资源非常大,而且申请一个新的进程的成本也非常高。所以后来就有了线程,它生成的成本和切换的消耗都比进程要低很多,而且线程间通信也非常方便。线程的问题是:1)存在线程安全问题,出了问题非常不易定位。2)进程内部有线程数目的限制。3)随着并发量的增加,线程生成和切换的成本也变得昂贵。
Python的协程实现:
协程可以用来做什么?
缺陷:
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使用的更多相关文章
- [转]Python中yield的解释
转自: http://python.jobbole.com/83610/ 本文作者: 伯乐在线 - wklken .未经作者许可,禁止转载!欢迎加入伯乐在线 专栏作者. 翻译 来源于stackover ...
- Python中yield和yield from的用法
yield python中yield的用法很像return,都是提供一个返回值,但是yield和return的最大区别在于,return一旦返回,则代码段执行结束,但是yield在返回值以后,会交出C ...
- Python Deque 模块使用详解,python中yield的用法详解
Deque模块是Python标准库collections中的一项. 它提供了两端都可以操作的序列, 这意味着, 你可以在序列前后都执行添加或删除. https://blog.csdn.net/qq_3 ...
- [翻译]Python中yield的解释
问题: Python中yield关键字的作用是什么?它做了什么? 例如,我想理解以下代码 def node._get_child_candidates(self, distance, min_dist ...
- python中yield用法
在介绍yield前有必要先说明下Python中的迭代器(iterator)和生成器(constructor). 一.迭代器(iterator) 在Python中,for循环可以用于Python中的任何 ...
- Python中yield深入理解
众所周知,python中的yield有这样的用法: def test(alist): for i in alist: yield i 这样,这个test函数就变成了一个生成器,当每次调用的时候,就会自 ...
- python中yield的用法
---"在python中,当你定义一个函数,使用了yield关键字时,这个函数就是一个生成器" (也就是说,只要有yield这个词出现,你在用def定义函数的时候,系统默认这就不是 ...
- Python中yield函数浅析
带有yield的函数在Python中被称之为generator(生成器),下面我们将使用斐波那契数列来举例说明下该函数:(环境是在Python3.x下) 如何生成斐波那契数列: 斐波那契(Fibon ...
- 关于Python中yield的一些个人见解
# 样例代码def yield_test(n): for i in range(n): yield call(i) print("i=",i) #做一些其它的事情 print(&q ...
随机推荐
- hdu1078(记忆化搜索)
题意:给出n*n的格子,每个各自里面有些食物,问一只老鼠每次走最多k步所能吃到的最多的食物 这道题目,值得我记住它,re了n次,以前写搜索没有注意的一个小地方,导致re这么多次的 ac代码: #inc ...
- java web hello world(二)基于Servlet理解监听
java web最开始实现是通过Servlet实现,这里就来实现下,最原始的监听是如何实现的. 第一步,创建一个基本的web项目 ,参见(java web hello world(一)) 第二步,we ...
- C# 注册表修改 立即生效 [转]
修改注册表后不重启计算机边生效. const int WM_SETTINGCHANGE = 0x001A; const int HWND_BROADCAST = 0xffff; IntPtr resu ...
- socket相关函数中断后重试
慢系统调用accept,read,write被信号中断时应该重试.对于accept,如果errno为ECONNABORTED,也应该重试. connect虽然也会阻塞,但被信号中断时不能立即重试,该s ...
- Entity Framework(六):数据迁移
在前面的几篇文章中,简单的介绍了如何使用Entity Framework的Code First模式创建数据库,但是,在前面的几篇文章中,我们都是通过使用数据库初始化策略来做,也就是每次先删除数据库然后 ...
- markdown 表格宽度调整
先在浏览器中看看 HTML是怎样的: 看起来似乎不错.回到 Markdown 上,在原表格前添加 CSS 代码,类似这样: <style> table th:first-of-type ...
- 察看下列JSP内容
察看下列JSP内容 <html><body> <% for (int i=0;i<3;i++){ %> out.print(i*2); <% } %&g ...
- 如果将一个类设置为abstract,则此类必须被继承使用
利用final定义方法:这样的方法为一个不可覆盖的方法. Public final void print(){}: 为了保证方法的一致性(即不被改变),可将方法用final定义. 如果在父类中有fin ...
- how to use novaclient python api
ref: http://docs.openstack.org/developer/python-novaclient/api.html
- Windows下安装Apache 2.2.21图文教程
https://www.jb51.net/article/52086.htm 本文详细介绍了在Windows平台上安装Apache的过程,希望对初次安装Apache的朋友有所帮助. 1. 软件准备 我 ...