多任务编程——协程
本文主讲:Python中关于协程相关的知识……
可迭代对象及其检测方法
为什么学习可迭代对象(迭代器)
答:因为学习迭代器是为了学习生成器,学习生成器是为了学习协程!
什么是可迭代对象
答:可迭代对象就是可以被for循环遍历的对象,因此可迭代也称为可遍历
可迭代(遍历)对象举例:列表、元组、字典、字符串、range()
不可迭代对象举例:自定义类对象(不包括内含迭代器的类对象)、函数、整数(int)
注意事项:遍历字典默认遍历的字典的key值
可迭代判断截图:
判断对象是否可迭代的方法
方法:isinstance(判断对象,Iterable)
判断实例:bool = isinstance(判断对象,Iterable)
注意事项:
1:在利用 isinstance(判断对象,Iterable)方法判断对象是否可迭代时,要先导入collections模块内的 Iterable类!!,即 from collections import Iterable语句 ,否则程序报错
2:Iterable是一个可迭代对象类,isinstance()判断方法就是判断目标对象是否为Iterable类的一个子类
3:判断对象是可迭代对象返回 True,不是返回 False
4:默认的自定义类对象是不可迭代的,但是如果类中含有__iter__( ) 方法,那么这个类创建的对象就是可迭代的,其中__iter__( ) 方法也称为迭代器
快速代码体验
初识迭代器 (iter( )魔法方法)
概念:类默认是不可迭代对象,但是在类中如果含有__iter__( ) 这个方法,那么这个类创建的对象就是可迭代对象,iter( ) 魔法方法就是迭代器
可迭代对象的本质:创建对象的所属类中含有迭代器(iter( )),可向外提供一个迭代器
注意事项:默认类是不可迭代的,如果一个类创建的对象判定为可迭代,那么这个对象所属的类中一定含有__iter__方法
快速代码体验:
迭代器及其使用方法
课前回顾:默认类创建的对象是不可迭代的,如果一个类创建的对象判定为可迭代,那么这个对象所属的类中一定含有__iter__方法(迭代器)
什么是迭代器?
概念:用 for 循环遍历可迭代对象过程中,要有一个“人”来记录遍历访问第几个数据了,并且返回数据,以便于下一次遍历时返回的是下一个数据,这是把帮助我们记录遍历第几个数据的“人”称为迭代器(iterable)
注意事项:
1:类创建的可迭代对象通过__iter__方法向我们提供了一个迭代器,我们在遍历一个可迭代对象时,其实是先获取可迭代对象提供的的迭代器,然后再通过这个迭代器依次获取对象中的每一个数据!
2:比如说列表是一个可迭代对象,那么列表会向外提供一个迭代器,这个迭代器会获取列表内每个数据用于遍历
3:只要是可迭代对象,都可以获取这个对象的迭代器
迭代器特点:
1:记录遍历的位置
2:提供下一个元素的值(配合next(迭代器)函数使用)
循环遍历可迭代对象依次获取数据的原理
列表(可迭代对象)→iter()函数获取迭代器→next(迭代器)函数依次获取迭代器内的数据!(执行几次next函数,获得几个可迭代对象数据)→捕获异常
获取可迭代对象的迭代器函数——iter()
功能:获取可迭代对象的迭代器
语法:iteratro_obj = iter(可迭代对象)
快速代码体验
next()函数
功能:获取迭代器中的下一个元素的数据
语法:print(next(迭代器对象))
注意事项:
1:调用了几次next(迭代器对象)函数,就打印几次迭代器内的数据
2:如果调用next()函数的次数大于迭代器内的数据数量,程序报错
快速代码体验:
for循环遍历可迭代对象的底层原理
第一步:iter(可迭代对象)方法获取可迭代对象的迭代器 即调用可迭代对象类内的__iter__( )方法,iter( )方法返回的是一个迭代器类创建的迭代器
第二步:利用next(迭代器)方法依次获取数据,即多次调用迭代器类里面的__next__( )函数获取数据,直到数据全部获取成功结束迭代
第三步:捕获异常,如果next()函数调用次数大于迭代器内的数据数,程序报错,捕获这个异常防止程序报错
快速代码体验(异常截图):
for循环底层原理截图:
自定义迭代器类
自定义迭代器类注意事项:
1:类里面必须含有__iter( )__方法
2:类里面必须含有 next( )方法
快速代码体验
自定义迭代对象、迭代器【重点、难点】
自定义一个可迭代对象类(列表)
构造必要条件:一个类默认是不可以迭代的,要想让一个类可迭代,类里面必须有 iter 方法(提供迭代器),其中提供的迭代器是另外定义的一个类,迭代器类里面必须包含__iter__ ( )方法与__next__()方法,而且自定义一个列表类,类里面还要有增加数据以及其他的基本方法,还有最起码的 __init__构造方法
自定义可迭代对象类(列表)条件清单:
1:两个类(自定义列表类与自定义迭代器类)
2:列表类里面的__iter__ ( )、init()、基本数据构造等方法
3:迭代器类里面的__iter__ ( )、next( )、init( )方法
注意事项:
1:自定义迭代器类是为自定义列表类的__iter__( )方法提供一个迭代器
条件截图:
自定义列表类代码截图(三段代码组成一段程序)
自定义可迭代对象的方法步骤
第一步:自定义可迭代对象类,内含__init__( )、 iter( )、 其他基本功能方法如添加数据等方法
第二步:自定义一个迭代器类,用来给自定义可迭代对象类提供一个迭代器,内含__init__( )、 iter( )、next( ) 方法
第三步:自定义可迭代对象类的__init( )方法要初始化一个容器保存数据,可以是列表、元组等
第四步:创建的可迭代对象类中的__iter__( )方法要返回创建的迭代类实例化的迭代器
第五步:创建的迭代器类里面的__next__( )方法要返回数据容器存放的数据!每调用一次返回一个数
据
第六步:创建的迭代器类里面的__next__( )方法要进行抛出异常即在遍历完成时结束迭代
常见问题【重点、重点】
目标:知道 a = b b = a + b 与 a ,b = b , a + b 的区别【重点】斐波那契数列必须知道这两个的区别才能书写代码
代码区别
a = b
b = a + b
上面代码先将 b 的值赋值给 a 在计算 b 的数值
————————————————————————————————————
a ,b = b , a + b
上面代码是先计算等号右面的数值,即 b 与 a + b 的数值,在赋值给等号左面类似于下面的代码
temp = b
b = a + b
a = temp
代码演示
迭代器应用——斐波那契数列【重点】
什么是斐波那契数列?
斐波那契数列实现原理:
创建一个迭代器类实现输出打印斐波那契数列
创建迭代器类满足条件:init( )、 iter( )、 next( )、三个函数
注意事项:
1:迭代器类本身就是一个迭代器,因此迭代器类创建的实例可以被for循环遍历
2:想要迭代器类创建的对象可以被for循环遍历,那么迭代器类里面的__iter__( )方法就要返回迭代器本身,即return self
斐波那契数列代码实现:
掌握斐波那契数列底层代码不能书写的样式
答:底层重要代码不能这么写 a = b b = a + b
生成器——基本使用
什么是生成器(generator)?
概念:生成器是一种特殊的迭代器(按照一定规律生成数列),是一种更加简洁的迭代器,生成器可以理解为可暂停的函数!
创建生成器两种方式:
1:根据列表推导式演变创建生成器 → generator_obj = ( i for i in range(10) )
2:函数中使用了 yield 关键字
注意事项:
1:生成器是特殊迭代器,因此生成器也可以用next(生成器)来获取数据
2:生成器也可以被遍历
快速代码体验(推导式创建生成器)
函数中使用 yield 创建生成器(函数为一个生成器)
生成器案例——斐波那契数列【重点】
利用函数内的 yield 关键字创建斐波那契数列生成器
注意事项:调用一次next(生成器对象)函数,就会执行一次生成器函数到 yield 关键字处返回数据且暂停程序,直到下一次调用next(生成器对象)函数或者send()函数才可以再次唤醒生成器程序函数继续执行!
代码截图
生成器函数内 yield 关键字功能
功能1:返回数据
功能2:暂停函数,不是退出函数,是暂停函数,这也是yield与return的区别!直到下一次调用next()函数时程序继续执行
也可以理解为遇到yield关键字记录位置退出函数,下一次调用next()函数从记录的位置继续执行代码!
生成器使用注意事项【send、return】
函数生成器中 return 的作用
功能:退出生成器函数,不在继续生成数据,当继续执行 next()函数时报错,报错内容就是 return 返回的数据内容,对此我们可以对下面执行的 next()进行异常捕获
注意事项:生成器内的 return 一般要配合 if 条件使用,在满足规定条件后才执行return语句,退出生成器函数
快速代码体验
send 方法启动生成器并且传递参数
send(参数)功能:可以唤醒暂停的生成器程序(生成器内部遇见 yield 关键字就会暂停,直到继续调用next()或send()函数),并且向生成器内传递参数即 参数 = yieid result
使用语法:生成器对象.send(参数)
快速代码体验
协程——基本使用(yield实现协程)
什么是协程
概念:协程可以理解为一种特殊的生成器,在不开辟其他子线程的情况下(只有主线程)可以实现多任务,并且协程有暂停函数的功能,且协程也称为微线程或者纤程
协程适用场景:在程序中存在大量不需要 cpu 的操作时,如I/O操作
线程与协程的差异:在实现多任务时,线程的切换非常耗性能,协程切换没有线程那么耗能
图示:
函数使用yield关键字可以实现协程
实现步骤:
1:创建两个生成器函数(内涵yield)
2:获取这两个生成器
3:value = next(生成器)来获取生成器返回的值
代码演示:
greenlet 实现协程
什么是 greenlet ?
答:greenlet 是 python 的一个 c 扩展,旨在提供一个可自行调度的 “微线程”(协程)方法
安装 greenlet 第三方库指令:pip3 install greenlet
怎么用 greenlet 实现协程多任务(步骤)
第一步:导入 greenlet 模块 from greenlet import greenlet(导入greenlet模块里面的greenlet类)
第二步:创建任务函数(不含yield 必须含有任务切换方法 switch())
第三步:创建协程 greenlet 对象,几个任务函数创建几个协程对象,并为协程对象指定函数任务(函数无括号) 即 协程对象 = greenlet(函数任务)
第四步:手动选择协程先执行哪个任务函数 即 协程对象.switch()
注意事项:
1:创建的任务函数一定要包含 switch() 切换任务函数
2:greenlet实现协程在只有主线程情况下进行多任务
快速代码体验:
gevent 实现协程【重中之重】
什么是 gevent
答:上一集我们用 greenlet 实现了协程多任务,但是多任务却要我们手动切换任务函数,不要捉急 python 有一个更为强大的第三方库 gevent 它可以实现自动切换任务函数
gevent 第三方库可以自动检测代码中哪些是耗时操作,遇到耗时较长的代码就会立刻切换到另一个任务去执行,然后在一个合适的时间返回执行刚开始的函数
gevent 怎么使用及实现协程多任务
gevent实现多任务步骤:
1:导入模块 import gevent
2:创建任务函数,任务函数内含耗时操作(gevent.sleep(0.5))
3:为 gevent 指派任务 并且返回一个 gevent 对象 即 对象 = gvenv.spawn(任务函数,参数1,参数2,…….),注意有几个任务就指派几次任务返回几个对象,先指派的哪个任务就先执行哪个任务函数
4:让主线程等待协程执行结束后在结束,即 gevent对象.join()
注意事项:
1:在任务函数中耗时操作为什么不能使用 time.sleep(0.5)只能使用 gevent.sleep(0.5),因为gevent只能识别自己的gevent.sleep(0.5)是耗时操作,不能识别time.sleep(0.5)是耗时操作,但是它们两个的效果是一样的
2:第四步的join()必须书写,否则在协程没有执行结束,主线程就结束了并且释放数据和内存,不能输出任何数据
3:耗时操作包括 I/O操作,比如文件的读取写入
快速代码体验:
给一个 gevent 不能识别为耗时操作的代码(time.sleep(0.5)),转变成可以识别为耗时代码操作(打补丁)
什么是打补丁:打补丁就是在不修改程序源代码的情况下为程序增加新的功能
给 gevent 打补丁:在不改变 gevent 原始代码的情况下,为 gevent 增加新的功能
利用打补丁操作将 gevent 不能识别为耗时操作的代码(time.sleep(0.5))识别为耗时操作步骤:
第一步:导入模块 from gevent import monkey
第二步:破解所有即可 monkey.patch_all( ) 这样就可以识别 time.sleep(0.5)代码为耗时操作了
注意事项:上面的这两行代码一般都写在一个程序的最上方!
快速代码体验:
猴子补丁功能:
1:在不修改第三方源代码的情况下增加原来不支持的功能
2:在运行时为内存中的对象增加补丁,而不是在磁盘的源代码中增加补丁
线程、进程、协程区别【重点】
进程、线程、协程
进程、线程、协程关系
线程、进程、协程应用场景
多进程:密集 cpu 任务,需要充分使用多核cpu资源
缺点:多个进程间通信成本高、切换开销大
多线程:密集 I/O任务(网络I/O,磁盘I/O,数据库I/O) 网络I/O即联网下载上传文件
缺点:同一个时间切片只能运行一个线程,不能做到高并行,能做到高并发
多协程:不需要大量 cup 操作时,应用于网络 I/O 下载时
缺点:单线程执行,处理大量cpu操作以及磁盘I/O操作时性能较低,但是处理网络I/O性能比较高
注意事项:性能最高的组合即进程+协程,进程可调度多个cpu
案例:并发下载器
urllib.request 第三方库的 urlopen(url) 方法
功能:打开指定 url 的网站并且返回一个类文件,这个类文件支持一般文件的操作比如说read()、readlin()等
注意事项:类文件对象 = urllib.reques.urlopen(url)返回一个类文件,但是要对这个类文件执行read()方法才能读取到其中的数据从而实现保存文件的结果
具体功能详解:https://www.cnblogs.com/langdashu/p/4963053.html
使用步骤:
第一步:导入模块 import urllib.request
第二步:返回类文件对象,类文件对象 = urllib.reques.urlopen(url)
第三步:对这个类文件对象进行操作(读取这个文件数据) 即:类文件对象.read()等操作
图示:
给所有的 gevent 协程任务 join()的方法 gevent.joinall()
功能:给所有指定任务的协程进行 join()
语法:gevent.joinall([协程任务1,协程任务2,协程任务3])
图示:
协程并发下载多种图片
原理图示:
实现并发下载器的步骤:
第一步:导入猴子补丁模块并且破解所有 即 from gevent import monkey monkey.patch()
第二步:导入urllib.request 与 gevent 模块
第三步:定义down_load()函数用于下载内容
1:获取图片的类文件
2:打开本地文件写入图片的二进制文件
3…
第四步:定义 main()函数保存下载内容的 url 网站 并且调用 down_load( )函数对文件进行下载
第五步:对 down_load( )函数进行异常捕获
代码演示:
案例:web协程服务器
略……
未完待续….