迭代器、生成器和协程

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. 微服务API网关

    当你选择采用微服务构建自己的程序,则你需要考虑客户端怎样与后端服务交互.对于一个单体应用,仅有一个服务群提供服务(通过负载均衡器实现).在微服务架构里面,每一个服务都暴漏了一个服务器集群.本篇文章我们 ...

  2. java后台读取/解析 excel表格

    需求描述 前台需要上传excel表格,提交到后台,后台解析并返回给前台,展示在前台页面上! 前台部分代码与界面 <th style="padding: 7px 1px;width:15 ...

  3. 【转】ArcGIS API for Silverlight/WPF 2.1学习笔记(二)

      五.Graphics layer 1.新增Graphics layer Graphics layer用于显示用户自定义绘制的点.线.面图形.使用时确保xaml文件中Graphics layer定义 ...

  4. mysql--------char 和 varchar 的区别

    char是一种固定长度的类型,varchar则是一种可变长度的类型,它们的区别是: char(M)类型的数据列里,每个值都占用M个字节,如果某个长度小于M,MySQL就会在它的右边用空格字符补足.(在 ...

  5. android----HttpClient的get,post和图片上传服务器

    HttpClient是Apache Jakarta Common下的子项目,用来提供高效的.最新的.功能丰富的支持HTTP协议的客户端编程工具包,并且它支持HTTP协议最新的版本和建议.HttpCli ...

  6. Bug in Code CodeForces - 420C (计数,图论)

    大意: 给定$n$结点无向图, 共n条边, 有重边无自环, 求有多少点对(u,v), 满足经过u和v的边数>=p 可以用双指针先求出所有$deg_u+deg_v \ge p$的点对, 但这样会多 ...

  7. iOS UI-界面传值(三种方法)

    #import <Foundation/Foundation.h> @interface DataModel : NSObject @property (nonatomic, copy) ...

  8. javascript数据结构——栈

    栈是一种高效的数据结构,数据只能在栈顶添加或删除,所以这样操作很快,也很容易实现.栈的使用遍布程序语言实现的方方面面,从表达式求值到处理函数调用.接下来,用JavaScript实现一个栈的数据结构. ...

  9. BZOJ1299 [LLH邀请赛]巧克力棒

    怎么又是博弈论...我去 Orz hzwer,这道题其实是可以转化成Nim游戏的! "第一步: 先从n根巧克力棒中取出m(m>0)根,使得这m根巧克力棒的xor和为0,同时使得剩下的n ...

  10. WEBSERVICE-CXF服务端代码

    Spring + cxf 发布webservice 依赖的jar包 WEB.xml的配置 applicationContext.xml配置   部署在Tomcat中启动   出现的问题         ...