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 ...
随机推荐
- java反射究竟消耗多少效率
原文出处 一直以来都对Java反射究竟消耗了多少效率很感兴趣,今晚总算有空进行了一下测试 测试被调用的类和方法 package com.spring.scran; public class TestM ...
- codeforces 930b//Game with String// Codeforces Round #468 (Div. 1)
题意:一个串,右循环移位后,告诉你第一个字母,还能告诉你一个,问你能确定移位后的串的概率. 用map记录每个字母出现的位置.对于每个字母,用arr[j][k]记录它的所有出现位置的后j位是字母k的个数 ...
- 关于controller中调用多个service方法的问题
一般service方法是有事务的,把所有操作封装在一个service方法中是比较安全的. 如果在controller中调用多个service方法,只有查询的情况下是可以这样的.
- vEthernet(默认交换机) 无法访问网络
VMware 开启虚拟机 最近公司由无线网转为有线时,我用VMware Workstation Pro装的几个系统,其中一个 ubuntu无法访问网页,在ubuntu运行ifconfig -a ,直接 ...
- 074——VUE中vuex之模块化modules开发实例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- beaglebone-black reference url
reference : https://github.com/beagleboard/beaglebone-black/wiki/System-Reference-Manual https://bea ...
- 弹出层小插件之(二) layer&layui
其实layer或者layui相对于上次所说的 sweetalert来说不仅仅有弹出层,它有很多的功能,这也大大的提高了我们的开发效率,根据我们项目的实际需要的效果进行选择.下面介绍下Layer的用法吧 ...
- 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, ...
- 玩转X-CTR100 l STM32F4 l 红外遥控接收
我造轮子,你造车,创客一起造起来!塔克创新资讯[塔克社区 www.xtark.cn ][塔克博客 www.cnblogs.com/xtark/ ] X-CTR100控制器具有红外接收头,例程 ...
- DevExpress v18.1新版亮点——WPF篇(五)
用户界面套包DevExpress v18.1日前终于正式发布,本站将以连载的形式为大家介绍各版本新增内容.本文将介绍了DevExpress WPF v18.1 的新功能,快来下载试用新版本!点击下载& ...