在说协程前,需要先了解在Python中常用迭代器和生成器,了解这两个能够有效的帮助你去理解协程的工作原理
迭代
概念:通过for循环遍历取值的过程叫迭代
可迭代对象:可用for循环遍历取值的对象,[列表、元组、字典、集合、range、字符串]
判断方式:
导入:form collections import Iterable
判断:result = isinstance(数据,Iterable[迭代对象类型]) 【返回boole值】
自定义迭代对象:
在类中定义__iter__方法创建的对象
注意:
1.只要有__iter__方法的类创建的对象是可迭代对象,可迭代对象需要迭代器完成数据迭代
迭代器
概念:在类里定义__iter__和__next__方法创建的对象就是迭代器对象
迭代器作用:记录当前数据位置,便于获取下一个位置的数据
优点:占用极小内存空间,存储的是生成数据的方式而不是结果
实现: 延迟计算 / 懒惰计算
判断方式:
导入:from collections import Iterator
判断:result = isinstance(数据,Iterator[迭代器对象类型])
自定义迭代器:
iter()函数:获取可迭代对象的迭代器,调用可迭代对象的__iter__方法
next()函数:获取迭代器中下一个值,会调用迭代器对象的__next__方法
注意:
1.迭代对象中的__iter__方法创建迭代器对象并返回
2.迭代器通过__next__获取下一个数据,数据取完后需要手动抛出停止迭代的异常(raise StopIteration)
3.循环通过迭代器获取数据时,需要进行异常捕获,避免程序停止报错
for循环的本质
遍历 可迭代对象
运行流程:for item in Iterable
1.通过 iter() 函数获取可迭代对象 Iterable 的迭代器
2.对获取的迭代器不断调用 next() 方法获取下一个值并赋值给item
3.当遇到 StopIteration 的异常后循环结束
遍历 迭代器
运行流程:for item in Iterator
1.循环的迭代器不断调用 next() 方法获取下一个值赋值给item
2.遇到Stoplteration异常结束循环
应用场景:
迭代器的核心功能是通过next()函数调用返回下一个数据值,如果数据来源不是已有的集合中,而是通过程序按照一定规律生成,那么可以不用依赖已有的数据集合,可以不用将所有要迭代的数据全部缓存后依次读取,可以节省大量的内存资源。
生成器
概念:不需要自己定义__iter__和__next__方法仍然可以使用next函数和for循环取值
场景:保证代码只指定一部分就返回
创建:
1.生成器表达式:将列表表达式的 [ ]更改为 () 【生成器存储的是算法,占用的内存更少】
2.函数生成器:使用了yield关键字
1.在yield下可以使用return,代码执行到return时,会停止迭代,抛出停止异常
【Python3才支持 return 关键字,可以通过except StopIteration as e: 捕获 e.value 输出】
两者之间区别:
1.使用了 yield 关键字的函数就是生成器
2.代码执行到yield会暂停挂起,将结果返回给调用生成器的位置,下次启动时从挂起位置继续
3.每次启动生成器都会返回一个值,yield可以返回多个值
4.return只能返回一次值,代码执行return 停止迭代
5.生成器的结束都是 StopIteration 异常抛出,所以需要提前设置好捕获
2.使用send方法启动生成器传参
异同:
next是函数send是方法
next不能传参send可以传参
第一次调用必须是next,后续可以是send<传递的参数在yield左边的变量获取
send执行时也是从yield位置开始>
注意:
1.单独获取生成器内容next(生成器对象)
2.for循环遍历后,直接输出结果,不需要next()】
协程
概念: 微线程、用户级线程,在def中有yield关键字就是协程
创建:
1.greenlet
【该类需要下载后导入,通过 greenlet.greenlet(方法名) 创建协程对象,对象.switch()进行调取方法,不过用的一般很少】
2.gevent(常用)
概述:内部封装了greenlet,遇到IO[input、output、网络、文件操作]操作时,自动切换其他的greenlet,操作完成,再切换回来继续执行
应用:
1.导入:from gevent import monkey
2.执行补丁:monkey.patch_all() 【使gevent识别耗时操作异步执行处理,必须放在代码开始处,避免加载问题】
3.创建协程对象:xx = gevent.spawn(方法名,位置参数(args),关键字参数(kwargs))
4.等待执行完成:xx.join() 【如果当前是无限循环且有耗时操作,可以不需要join】
5.批量进程等待:gevent.joinall([对象1],[对象2])
进程、线程、协程对比
1.关联
一个进程至少有一个线程,进程里面可以有多个线程
一个线程里面可以 有多个协程
2.对比
1.进程是资源分配的基础单位
2.线程是操作系统调度的基本单位
3.进程切换需要的资源最大,效率很低
4.线程切换需要的资源一般,效率一般(不基于GIL的情况下)
5.线程切换任务资源很小,效率高
6.多进程、多线程根据cpu核数不一样可能是并行,但是协程是一个线程中,所以是并发
7.进程和线程都是由系统进行控制执行,而协程有程序员控制
8.进程适用于CPU密集不需要切换,大量并发计算的情况
9.多线程适用IO密集,网络IO和磁盘IO
10.协程适用于:密集网络IO,适合网络IO