迭代器、生成器和协程

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-迭代器、生成器和协程的更多相关文章

  1. Python入门之迭代器/生成器/yield的表达方式/面向过程编程

    本章内容 迭代器 面向过程编程 一.什么是迭代 二.什么是迭代器 三.迭代器演示和举例 四.生成器yield基础 五.生成器yield的表达式形式 六.面向过程编程 ================= ...

  2. 【Python】【容器 | 迭代对象 | 迭代器 | 生成器 | 生成器表达式 | 协程 | 期物 | 任务】

    Python 的 asyncio 类似于 C++ 的 Boost.Asio. 所谓「异步 IO」,就是你发起一个 IO 操作,却不用等它结束,你可以继续做其他事情,当它结束时,你会得到通知. Asyn ...

  3. python基础----迭代器、生成器、协程函数及应用(面向过程实例)

    一.什么是迭代器协议 1.迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代 (只能往后走不能往前退) 2.可迭代 ...

  4. python day 20: 线程池与协程,多进程TCP服务器

    目录 python day 20: 线程池与协程 2. 线程 3. 进程 4. 协程:gevent模块,又叫微线程 5. 扩展 6. 自定义线程池 7. 实现多进程TCP服务器 8. 实现多线程TCP ...

  5. Python之线程、进程和协程

    python之线程.进程和协程 目录: 引言 一.线程 1.1 普通的多线程 1.2 自定义线程类 1.3 线程锁 1.3.1 未使用锁 1.3.2 普通锁Lock和RLock 1.3.3 信号量(S ...

  6. python yield、yield from与协程

    从生成器到协程 协程是指一个过程,这个过程与调用方协作,产出由调用方提供的值.生成器的调用方可以使用 .send(...)方法发送数据,发送的数据会成为yield表达式的值.因此,生成器可以作为协程使 ...

  7. python自动化开发学习 进程, 线程, 协程

    python自动化开发学习 进程, 线程, 协程   前言 在过去单核CPU也可以执行多任务,操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换任务2,任务2执行0.01秒,在切换到任务3,这 ...

  8. python爬虫---单线程+多任务的异步协程,selenium爬虫模块的使用

    python爬虫---单线程+多任务的异步协程,selenium爬虫模块的使用 一丶单线程+多任务的异步协程 特殊函数 # 如果一个函数的定义被async修饰后,则该函数就是一个特殊的函数 async ...

  9. python装饰器,迭代器,生成器,协程

    python装饰器[1] 首先先明白以下两点 #嵌套函数 def out1(): def inner1(): print(1234) inner1()#当没有加入inner时out()不会打印输出12 ...

随机推荐

  1. wpf里窗体嵌入winform控件被覆盖问题

      问题1:嵌套Winform控件(ZedGraph)在WPF的ScrollViewer控件上,出现滚动条,无论如何设置该Winform控件都在顶层,滚动滚动条会覆盖其他WPF控件. 解决办法:在Sc ...

  2. English trip -- VC(情景课) 6 A Time

    Words eleven  十一 twelve  十二 thirteen  十三 fourteen fifteen sixteen seventeen eighteen nineteen twenty ...

  3. 4-6 select_tag和select的区别和理解。javascript_tag

    via: :all是什么意思?主要用于约束http动作. <%= select_tag "set_locale", options_for_select(LANGUAGES, ...

  4. Java 本地环境设置

    如果你希望在你的本地环境中设置 Java 程序环境,下面的这部分将会指导你在你的本地计算机上下载和设置 Java 环境.你可以按照下面的步骤进行. Java SE 目前是免费下载的,你可以通过单击下面 ...

  5. bzoj4919 大根堆

    考虑二分求序列LIS的过程. g[i]表示长度为i的LIS最小以多少结尾. 对于每个数,二分寻找插入的位置来更新g数组. 放到树上也是一样,额外加上一个合并儿子的过程. 发现儿子与儿子直接是互不影响的 ...

  6. en_java去重排序

    @Test public void tes5() { String[] strs ={"e", "ee", "ea", "ei&q ...

  7. httpclient 连接参数

    http.socket.timeout(读取超时) 套接字毫秒级超时时间(SO_TIMEOUT),这就是等待数据,换句话说,在两个连续的数据包之间最大的闲置时间. 如果超时是0表示无限大的超时时间,即 ...

  8. learning uboot part command

    => part --helppart - disk partition related commands Usage:part uuid <interface> <dev> ...

  9. 快速切题 poj1068

    Parencodings Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 19716   Accepted: 11910 De ...

  10. Flask初级(一)创建及运行flash

    和前面的django差不多,选个类型,定义个目录. 选个模板解释器,定义一个模板文件夹名称. Create就创建成功了. 运行一下.会显示 Hello World! 最好给它换个运行环境,建个虚拟环境 ...