迭代器、生成器和协程

python中任意的对象,只要它定义了可以返回一个迭代器的__iter__方法,或者支持下标索引的_getitem_方法,那么它就是一个可迭代对象。

可迭代的对象不一定就是迭代器;

比如:一个列表L=[1,2,3]是一个可迭代对象,但不是迭代器,l=iter(L)返回的是迭代器;

  1. In [17]: L = [1,2,3,4]
  2.  
  3. In [18]: l = iter(L)
  4.  
  5. In [19]: next(l)
  6. Out[19]: 1
  7.  
  8. In [20]: next(l)
  9. Out[20]: 2
  10.  
  11. In [21]: l.__next__()
  12. Out[21]: 3
  13.  
  14. In [22]: l.__next__()
  15. Out[22]: 4
  16.  
  17. In [23]: l.__next__()
  18. ---------------------------------------------------------------------------
  19. StopIteration Traceback (most recent call last)
  20. <ipython-input-23-731686253790> in <module>()
  21. ----> 1 l.__next__()
  22.  
  23. StopIteration:

什么是迭代器呢?

实现了__iter__和next的方法的对象就是迭代器,其中,__iter__方法但会迭代器对象本身,next方法返回容器的下一个元素,在没有后续元素时抛出StopIteration异常;

在python2中要定义的next方法名字不同,应该是__next__

为什么有了可迭代对象,还要有迭代器?

因为可迭代对象,是全部获取,占用内存;也是因为使用迭代器更通用,更简单、优雅;

迭代器可以用list函数转换为一个列表;

  1. In [5]: class Fib:
  2. ...: def __init__(self,max):
  3. ...: self.a = 0
  4. ...: self.b = 1
  5. ...: self.max = max
  6. ...:
  7. ...: def __iter__(self):
  8. ...: return self
  9. ...:
  10. ...: def __next__(self):
  11. ...: fib = self.a
  12. ...: if fib > self.max:
  13. ...: raise StopIteration
  14. ...: self.a,self.b = self.b,self.a + self.b
  15. ...: return fib
  16. ...:
  17.  
  18. In [6]: f = Fib(100)
  19.  
  20. In [7]: for i in f:
  21. ...: print(i)
  22. ...:
  23. 0
  24. 1
  25. 1
  26. 2
  27. 3
  28. 5
  29. 8
  30. 13
  31. 21
  32. 34
  33. 55
  34. 89

In [45]: f.next()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-45-297e922ec2d6> in <module>()
----> 1 f.next()

  1.  

AttributeError: 'Fib' object has no attribute 'next'

  1.  

In [46]: next(f)
Out[46]: 0

  1.  

In [47]: next(f)
Out[47]: 1

  1.  

In [48]: next(f)
Out[48]: 1

  1.  

In [51]: f.__next__()

Out[51]: 2

  1.  

In [52]: f.__next__()

Out[52]: 3

  1. In [8]: list(Fib(100)) #迭代器可以用list函数转换为一个列表;
  2. Out[8]: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

生成器

生成器是一种使用普通函数语法定义的迭代器。生成器和普通函数的区别是使用yield,而不是return返回值。

yield 中文有生产、产生的意思;

yield每次返回一个结果,在每个结果的中间会挂起函数;挂起函数的状态便于下次从离开的地方继续;生成器本质上也是迭代器;

对于 Python 生成器中的 yield 来说,yield item 这行代码会产出一个值,提供给 next(...) 的调用方;此外,还会作出让步,暂停执行生成器,让调用方继续工作,直到需要使用另一个值时再调用next();调用方会从生成器中拉取值。

生成器表达式

  1. In [24]: g = (i for i in range(10) if i%2) #生成器表达式
  2.  
  3. In [25]: g
  4. Out[25]: <generator object <genexpr> at 0x7f2db837a620>
  5.  
  6. In [26]: for i in g:
  7. ...: print(i)
  8. ...:
  9. 1
  10. 3
  11. 5
  12. 7
  13. 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 视作控制流程的方式 ,这样就好理解协程了。

来看个例子:

  1. n [27]: def coroutine2(a):
  2. ...: print(f'Start: {a}')
  3. ...: b = yield a
  4. ...: print(f'Received: b = {b}')
  5. ...: c = yield a + b
  6. ...: print(f'Received: c = {c}')
  7. ...:
  8.  
  9. In [28]: coro = coroutine2(1)
  10.  
  11. In [29]: next(coro) #最先调用 next(my_coro) 函数 这一步通常称为 “预激”(prime)协程 (即,让协程向前执行到第一个 yield 表达式,准备好作为活跃的协程使用)
  12. Start: 1
  13. Out[29]: 1
  14.  
  15. In [30]: coro.send(2)
  16. Received: b = 2
  17. Out[30]: 3
  18.  
  19. In [31]: coro.send(10)
  20. Received: c = 10
  21. ---------------------------------------------------------------------------
  22. StopIteration Traceback (most recent call last)
  23. <ipython-input-31-7a1f101c1ec1> in <module>()
  24. ----> 1 coro.send(10)
  25.  
  26. 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 之外的值发给它,会出现下述错误:

  1. In [32]: coro2 = coroutine2(1)
  2.  
  3. In [33]: coro2.send(121)
  4. ---------------------------------------------------------------------------
  5. TypeError Traceback (most recent call last)
  6. <ipython-input-33-25e001dbff4a> in <module>()
  7. ----> 1 coro2.send(121)
  8.  
  9. TypeError: can't send non-None value to a just-started generator
  10.  
  11. In [34]: coro2.send(None)
  12. Start: 1
  13. 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跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高:

  1. import time
  2.  
  3. def consumer():
  4. r = ''
  5. while True:
  6. n = yield r
  7. print('[CONSUMER] Consuming %s...' % n)
  8. r = '200 OK'
  9.  
  10. def produce(c):
  11. next(c)
  12. n = 0
  13. while n < 5:
  14. n = n + 1
  15. print('[PRODUCER] Producing %s...' % n)
  16. r = c.send(n)
  17. print('[PRODUCER] Consumer return: %s' % r)
  18. c.close()
  19.  
  20. if __name__ == "__main__":
  21. c = consumer()
  22. produce(c)
  23.  
  24. 输出:
  25. [PRODUCER] Producing 1...
  26. [CONSUMER] Consuming 1...
  27. [PRODUCER] Consumer return: 200 OK
  28. [PRODUCER] Producing 2...
  29. [CONSUMER] Consuming 2...
  30. [PRODUCER] Consumer return: 200 OK
  31. [PRODUCER] Producing 3...
  32. [CONSUMER] Consuming 3...
  33. [PRODUCER] Consumer return: 200 OK
  34. [PRODUCER] Producing 4...
  35. [CONSUMER] Consuming 4...
  36. [PRODUCER] Consumer return: 200 OK
  37. [PRODUCER] Producing 5...
  38. [CONSUMER] Consuming 5...
  39. [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. java反射究竟消耗多少效率

    原文出处 一直以来都对Java反射究竟消耗了多少效率很感兴趣,今晚总算有空进行了一下测试 测试被调用的类和方法 package com.spring.scran; public class TestM ...

  2. codeforces 930b//Game with String// Codeforces Round #468 (Div. 1)

    题意:一个串,右循环移位后,告诉你第一个字母,还能告诉你一个,问你能确定移位后的串的概率. 用map记录每个字母出现的位置.对于每个字母,用arr[j][k]记录它的所有出现位置的后j位是字母k的个数 ...

  3. 关于controller中调用多个service方法的问题

    一般service方法是有事务的,把所有操作封装在一个service方法中是比较安全的. 如果在controller中调用多个service方法,只有查询的情况下是可以这样的.

  4. vEthernet(默认交换机) 无法访问网络

    VMware 开启虚拟机 最近公司由无线网转为有线时,我用VMware Workstation Pro装的几个系统,其中一个 ubuntu无法访问网页,在ubuntu运行ifconfig -a ,直接 ...

  5. 074——VUE中vuex之模块化modules开发实例

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  6. beaglebone-black reference url

    reference : https://github.com/beagleboard/beaglebone-black/wiki/System-Reference-Manual https://bea ...

  7. 弹出层小插件之(二) layer&layui

    其实layer或者layui相对于上次所说的 sweetalert来说不仅仅有弹出层,它有很多的功能,这也大大的提高了我们的开发效率,根据我们项目的实际需要的效果进行选择.下面介绍下Layer的用法吧 ...

  8. poj 3984 -- 迷宫问题 深搜

    迷宫问题 Description 定义一个二维数组: int maze[5][5] = { 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, ...

  9. 玩转X-CTR100 l STM32F4 l 红外遥控接收

    我造轮子,你造车,创客一起造起来!塔克创新资讯[塔克社区 www.xtark.cn ][塔克博客 www.cnblogs.com/xtark/ ]      X-CTR100控制器具有红外接收头,例程 ...

  10. DevExpress v18.1新版亮点——WPF篇(五)

    用户界面套包DevExpress v18.1日前终于正式发布,本站将以连载的形式为大家介绍各版本新增内容.本文将介绍了DevExpress WPF v18.1 的新功能,快来下载试用新版本!点击下载& ...