python入门20180717-迭代器、生成器和协程
迭代器、生成器和协程
python中任意的对象,只要它定义了可以返回一个迭代器的__iter__方法,或者支持下标索引的_getitem_方法,那么它就是一个可迭代对象。
可迭代的对象不一定就是迭代器;
比如:一个列表L=[1,2,3]是一个可迭代对象,但不是迭代器,l=iter(L)返回的是迭代器;
In [17]: L = [1,2,3,4] In [18]: l = iter(L) In [19]: next(l)
Out[19]: 1 In [20]: next(l)
Out[20]: 2 In [21]: l.__next__()
Out[21]: 3 In [22]: l.__next__()
Out[22]: 4 In [23]: l.__next__()
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-23-731686253790> in <module>()
----> 1 l.__next__() StopIteration:
什么是迭代器呢?
实现了__iter__和next的方法的对象就是迭代器,其中,__iter__方法但会迭代器对象本身,next方法返回容器的下一个元素,在没有后续元素时抛出StopIteration异常;
在python2中要定义的next方法名字不同,应该是__next__
为什么有了可迭代对象,还要有迭代器?
因为可迭代对象,是全部获取,占用内存;也是因为使用迭代器更通用,更简单、优雅;
迭代器可以用list函数转换为一个列表;
In [5]: class Fib:
...: def __init__(self,max):
...: self.a = 0
...: self.b = 1
...: self.max = max
...:
...: def __iter__(self):
...: return self
...:
...: def __next__(self):
...: fib = self.a
...: if fib > self.max:
...: raise StopIteration
...: self.a,self.b = self.b,self.a + self.b
...: return fib
...: In [6]: f = Fib(100) In [7]: for i in f:
...: print(i)
...:
0
1
1
2
3
5
8
13
21
34
55
89
In [45]: f.next()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-45-297e922ec2d6> in <module>()
----> 1 f.next()
AttributeError: 'Fib' object has no attribute 'next'
In [46]: next(f)
Out[46]: 0
In [47]: next(f)
Out[47]: 1
In [48]: next(f)
Out[48]: 1
In [51]: f.__next__()
Out[51]: 2
In [52]: f.__next__()
Out[52]: 3
In [8]: list(Fib(100)) #迭代器可以用list函数转换为一个列表;
Out[8]: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
生成器
生成器是一种使用普通函数语法定义的迭代器。生成器和普通函数的区别是使用yield,而不是return返回值。
yield 中文有生产、产生的意思;
yield每次返回一个结果,在每个结果的中间会挂起函数;挂起函数的状态便于下次从离开的地方继续;生成器本质上也是迭代器;
对于 Python 生成器中的 yield 来说,yield item 这行代码会产出一个值,提供给 next(...) 的调用方;此外,还会作出让步,暂停执行生成器,让调用方继续工作,直到需要使用另一个值时再调用next();调用方会从生成器中拉取值。
生成器表达式
In [24]: g = (i for i in range(10) if i%2) #生成器表达式 In [25]: g
Out[25]: <generator object <genexpr> at 0x7f2db837a620> In [26]: for i in g:
...: print(i)
...:
1
3
5
7
9
In [37]: g = (i for i in range(10) if i%2)
In [38]: g.next()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-38-7dbbdfed0980> in <module>()
----> 1 g.next()
AttributeError: 'generator' object has no attribute 'next'
In [39]: g
Out[39]: <generator object <genexpr> at 0x7f2dbb968570>
In [40]: next(g)
Out[40]: 1
In [41]: next(g)
Out[41]: 3
In [42]: next(g)
Out[42]: 5
从句法上看,协程与生成器类似,都是定义体中包含 yield 关键字的函数。可是,在协程中,yield 通常出现在表达式的右边(例如,datum = yield),可以产出值,也可以不产出——如果 yield关键字后面没有表达式,那么生成器产出 None。协程可能会从调用方接收数据,不过调用方把数据提供给协程使用的是 .send(datum) 方法,而不是 next(...) 函数。通常,调用方会把值推送给协程。
yield 关键字甚至还可以不接收或传出数据。不管数据如何流动,yield 都是一种流程控制工具,使用它可以实现协作式多任务:协程可以把控制器让步给中心调度程序,从而激活其他的协程。
从根本上把 yield 视作控制流程的方式 ,这样就好理解协程了。
来看个例子:
n [27]: def coroutine2(a):
...: print(f'Start: {a}')
...: b = yield a
...: print(f'Received: b = {b}')
...: c = yield a + b
...: print(f'Received: c = {c}')
...: In [28]: coro = coroutine2(1) In [29]: next(coro) #最先调用 next(my_coro) 函数 这一步通常称为 “预激”(prime)协程 (即,让协程向前执行到第一个 yield 表达式,准备好作为活跃的协程使用)
Start: 1
Out[29]: 1 In [30]: coro.send(2)
Received: b = 2
Out[30]: 3 In [31]: coro.send(10)
Received: c = 10
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-31-7a1f101c1ec1> in <module>()
----> 1 coro.send(10) StopIteration:
协程可以身处四个状态中的一个:
当前状态可以使用inspect.getgeneratorstate(...) 函数确定,该函数会返回下述字符串中的一个。
GEN_CREATED | 等待开始执行 |
GEN_RUNNING | 解释器正在执行 |
GEN_SUSPENDED | 在 yield 表达式处暂停 |
GEN_CLOSED | 执行结束 |
因为 send 方法 的参数 会成为暂停的 yield 表达式的值 ,所以,仅当协程处于暂停状态时才能调用 send 方法,例如 my_coro.send(10)。不过,如果协程还没激活(即,状态是 'GEN_CREATED'),情况就不同了。因此,始终要调用 next(my_coro) 激活协程——也可以调用my_coro.send(None),效果一样。
最先调用 next(my_coro) 函数 这一步通常称为 “预激”(prime)协程 (即,让协程向前执行到第一个 yield 表达式,准备好作为活跃的协程使用)。
如果创建协程对象后立即把 None 之外的值发给它,会出现下述错误:
In [32]: coro2 = coroutine2(1) In [33]: coro2.send(121)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-33-25e001dbff4a> in <module>()
----> 1 coro2.send(121) TypeError: can't send non-None value to a just-started generator In [34]: coro2.send(None)
Start: 1
Out[34]: 1
关键的一点是, 协程在 yield 关键字所在的位置暂停执行 。前面说过, 在赋值语句中, = 右边的代码在赋值之前执行 。因此,对于 b =yield a 这行代码来说,等到客户端代码再激活协程时才会设定 b 的值。这种行为要花点时间才能习惯,不过一定要理解,这样才能弄懂异步编程中 yield 的作用。
simple_coro2 协程的执行过程分为 3 个阶段,如图下图所示。
(1) 调用 next(my_coro2),打印第一个消息,然后执行 yield a,产 出数字 14。
(2) 调用 my_coro2.send(28),把 28 赋值给 b,打印第二个消息,然 后执行 yield a + b,产出数字 42。
(3) 调用 my_coro2.send(99),把 99 赋值给 c,打印第三个消息,协 程终止。
注意,各个阶段都在yield 表达式中结束,而且下一个阶段都从那一行代码开始,然后再把 yield 表达式的值赋给变量。
协程的特点及优势
协程的特点在于是一个线程执行,那和多线程比,协程有何优势?
最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
Python对协程的支持还非常有限,用在generator中的yield可以一定程度上实现协程。虽然支持不完全,但已经可以发挥相当大的威力了。
来看例子:
传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。
如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高:
import time def consumer():
r = ''
while True:
n = yield r
print('[CONSUMER] Consuming %s...' % n)
r = '200 OK' def produce(c):
next(c)
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s...' % n)
r = c.send(n)
print('[PRODUCER] Consumer return: %s' % r)
c.close() if __name__ == "__main__":
c = consumer()
produce(c) 输出:
[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consumer return: 200 OK
整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。
python入门20180717-迭代器、生成器和协程的更多相关文章
- Python入门之迭代器/生成器/yield的表达方式/面向过程编程
本章内容 迭代器 面向过程编程 一.什么是迭代 二.什么是迭代器 三.迭代器演示和举例 四.生成器yield基础 五.生成器yield的表达式形式 六.面向过程编程 ================= ...
- 【Python】【容器 | 迭代对象 | 迭代器 | 生成器 | 生成器表达式 | 协程 | 期物 | 任务】
Python 的 asyncio 类似于 C++ 的 Boost.Asio. 所谓「异步 IO」,就是你发起一个 IO 操作,却不用等它结束,你可以继续做其他事情,当它结束时,你会得到通知. Asyn ...
- python基础----迭代器、生成器、协程函数及应用(面向过程实例)
一.什么是迭代器协议 1.迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代 (只能往后走不能往前退) 2.可迭代 ...
- python day 20: 线程池与协程,多进程TCP服务器
目录 python day 20: 线程池与协程 2. 线程 3. 进程 4. 协程:gevent模块,又叫微线程 5. 扩展 6. 自定义线程池 7. 实现多进程TCP服务器 8. 实现多线程TCP ...
- Python之线程、进程和协程
python之线程.进程和协程 目录: 引言 一.线程 1.1 普通的多线程 1.2 自定义线程类 1.3 线程锁 1.3.1 未使用锁 1.3.2 普通锁Lock和RLock 1.3.3 信号量(S ...
- python yield、yield from与协程
从生成器到协程 协程是指一个过程,这个过程与调用方协作,产出由调用方提供的值.生成器的调用方可以使用 .send(...)方法发送数据,发送的数据会成为yield表达式的值.因此,生成器可以作为协程使 ...
- python自动化开发学习 进程, 线程, 协程
python自动化开发学习 进程, 线程, 协程 前言 在过去单核CPU也可以执行多任务,操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换任务2,任务2执行0.01秒,在切换到任务3,这 ...
- python爬虫---单线程+多任务的异步协程,selenium爬虫模块的使用
python爬虫---单线程+多任务的异步协程,selenium爬虫模块的使用 一丶单线程+多任务的异步协程 特殊函数 # 如果一个函数的定义被async修饰后,则该函数就是一个特殊的函数 async ...
- python装饰器,迭代器,生成器,协程
python装饰器[1] 首先先明白以下两点 #嵌套函数 def out1(): def inner1(): print(1234) inner1()#当没有加入inner时out()不会打印输出12 ...
随机推荐
- wpf里窗体嵌入winform控件被覆盖问题
问题1:嵌套Winform控件(ZedGraph)在WPF的ScrollViewer控件上,出现滚动条,无论如何设置该Winform控件都在顶层,滚动滚动条会覆盖其他WPF控件. 解决办法:在Sc ...
- English trip -- VC(情景课) 6 A Time
Words eleven 十一 twelve 十二 thirteen 十三 fourteen fifteen sixteen seventeen eighteen nineteen twenty ...
- 4-6 select_tag和select的区别和理解。javascript_tag
via: :all是什么意思?主要用于约束http动作. <%= select_tag "set_locale", options_for_select(LANGUAGES, ...
- Java 本地环境设置
如果你希望在你的本地环境中设置 Java 程序环境,下面的这部分将会指导你在你的本地计算机上下载和设置 Java 环境.你可以按照下面的步骤进行. Java SE 目前是免费下载的,你可以通过单击下面 ...
- bzoj4919 大根堆
考虑二分求序列LIS的过程. g[i]表示长度为i的LIS最小以多少结尾. 对于每个数,二分寻找插入的位置来更新g数组. 放到树上也是一样,额外加上一个合并儿子的过程. 发现儿子与儿子直接是互不影响的 ...
- en_java去重排序
@Test public void tes5() { String[] strs ={"e", "ee", "ea", "ei&q ...
- httpclient 连接参数
http.socket.timeout(读取超时) 套接字毫秒级超时时间(SO_TIMEOUT),这就是等待数据,换句话说,在两个连续的数据包之间最大的闲置时间. 如果超时是0表示无限大的超时时间,即 ...
- learning uboot part command
=> part --helppart - disk partition related commands Usage:part uuid <interface> <dev> ...
- 快速切题 poj1068
Parencodings Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 19716 Accepted: 11910 De ...
- Flask初级(一)创建及运行flash
和前面的django差不多,选个类型,定义个目录. 选个模板解释器,定义一个模板文件夹名称. Create就创建成功了. 运行一下.会显示 Hello World! 最好给它换个运行环境,建个虚拟环境 ...