迭代器、生成器和协程

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. 笔试题-sql语句

    今天遇到了不熟练(不会)的查询题目 回来自己又做了一下,如下 建表语句 -- Table structure for score -- ---------------------------- DRO ...

  2. 用EL時(el-api.jar,el-ri.jar ),要設isELIgnored="false"

    用EL時(el-api.jar,el-ri.jar ),要設isELIgnored="false" 否则jstl标签不显示. 加上 <%@page isELIgnored="false ...

  3. GetTextAndImageCreateExamPaper

    Private Declare Function URLDownloadToFile Lib "urlmon" Alias "URLDownloadToFileA&quo ...

  4. (转)(HR面试)最常见的面试问题和技巧性答复

    (HR面试)最常见的面试问题和技巧性答复 1.请你自我介绍一下你自己? 回答提示:一般人回答这个问题过于平常,只说姓名.年龄.爱好.工作经验,这些在简历上都有.其实,企业最希望知道的是求职者能否胜任工 ...

  5. ZOJ-3329 One Person Game (有环期望问题)

    题目大意:有3个骰子,各有k1,k2,k3个面,面值为1~ki.还有一个计数器,初始值为0,统计所有的面值和.每次同时置这三个骰子,如果第一个骰子的朝上的值为a.第二个值为b.第三个值为c,那么将计数 ...

  6. C#窗体控件简介ListBox(需整理)

    ListBox 控件 ListBox 控件又称列表框,它显示一个项目列表供用户选择.在列表框中,用户 一次可以选择一项,也可以选择多项. 1.常用属性: (1) Items属性: 用于存放列表框中的列 ...

  7. gvim配置文件

      vimrc配置 source $VIMRUNTIME/vimrc_example.vim source $VIMRUNTIME/mswin.vim behave mswin "设置文件的 ...

  8. elasticsearch term match multi_match区别

    转自:http://www.cnblogs.com/yjf512/p/4897294.html match 最简单的一个match例子: 查询和"我的宝马多少马力"这个查询语句匹配 ...

  9. 015PHP文件处理——文件处理flock 文件锁定 pathinfo realpath tmpfile tempname

    <?php /**文件处理flock 文件锁定 pathinfo realpath tmpfile tempname */ /*$arr=pathinfo('ab.txt');//获取文件路径的 ...

  10. HDU 1710 二叉树遍历

    首先.先序遍历是先访问根节点.然后左节点 然后右节点.从根节点开始 直到它的子节点没有左节点才开始回溯访问上一个节点的右节点.同理.中序遍历 先访问左节点 然后是父节点 然后是右节点.从根节点开始 直 ...