韩剧太阳的女儿,315晚会视频,霍林郭勒地图
前言
协程的核心点在于协程的使用,即只需要了解怎么使用协程即可;但如果你想了解协程是怎么实现的,就需要了解依次了解可迭代,迭代器,生成器了;
如果你只想看协程的使用,那么只需要看第一部分内容就行了;如果如果想理解协程,可以按照顺序依次阅读本博文,或者按照 迭代器-生成器-协程的顺序阅读。
上面的概念会在后面的知识点进行讲解;
要使用greenlet,首先要安装greenlet
pip3 install greenlet
greenlet实现多任务代码
from greenlet import greenlet import time def task1(): while 1: print("---1---") gr2.switch() time.sleep(1) def task2(): while 1: print("---2---") gr1.switch() time.sleep(1) gr1 = greenlet(task1) gr2 = greenlet(task2) # 切换到gr1中执行 gr1.switch()
但注意,这里其实是一个单线程;并且经过测试,这里最后几句不能使用 __main__ ,否则会报错;
可以看到,greenlet已经可以实现协程了,但需要我们手动进行任务切换,这样会很麻烦,因此我们要学习gevent,在greenlet的基础上进行了封装,可以帮助我们实现自动切换任务;
要使用gevent,使用要进行安装
pip3 install gevent
gevent实现多任务代码
import time import gevent def test1(n): for i in range(n): print("---test1---", gevent.getcurrent(), i) # time.sleep(0.5) # 这里使用time的sleep并不会因为耗时导致切换任务 gevent.sleep(0.5) def test2(n): for i in range(n): print("---test2---", gevent.getcurrent(), i) # time.sleep(0.5) # 这里使用time的sleep并不会因为耗时导致切换任务 gevent.sleep(0.5) def test3(n): for i in range(n): print("---test3---", gevent.getcurrent(), i) # time.sleep(0.5) # 这里使用time的sleep并不会因为耗时导致切换任务 gevent.sleep(0.5) g1 = gevent.spawn(test1, 5) g2 = gevent.spawn(test2, 5) g3 = gevent.spawn(test3, 5) g1.join() g2.join() g3.join()
运行结果:
---test1--- <greenlet at 0x1e9e64c2598: test1(5)> 0 ---test2--- <greenlet at 0x1e9e64c26a8: test2(5)> 0 ---test3--- <greenlet at 0x1e9e64c27b8: test3(5)> 0 ---test1--- <greenlet at 0x1e9e64c2598: test1(5)> 1 ---test2--- <greenlet at 0x1e9e64c26a8: test2(5)> 1 ---test3--- <greenlet at 0x1e9e64c27b8: test3(5)> 1 ---test1--- <greenlet at 0x1e9e64c2598: test1(5)> 2 ---test2--- <greenlet at 0x1e9e64c26a8: test2(5)> 2 ---test3--- <greenlet at 0x1e9e64c27b8: test3(5)> 2 ---test1--- <greenlet at 0x1e9e64c2598: test1(5)> 3 ---test2--- <greenlet at 0x1e9e64c26a8: test2(5)> 3 ---test3--- <greenlet at 0x1e9e64c27b8: test3(5)> 3 ---test1--- <greenlet at 0x1e9e64c2598: test1(5)> 4 ---test2--- <greenlet at 0x1e9e64c26a8: test2(5)> 4 ---test3--- <greenlet at 0x1e9e64c27b8: test3(5)> 4
g1.join()表示等待g1执行完成;当我们使用spawn创建一个对象时,并不会去执行该协程,而是当主线程走到等待g1完成时,这里需要等待时间,我们就去执行协程。
注意,在gevent中如果要使用sleep(),必须要使用 gevent.sleep();
存在一个问题当我们创建g1,g2,g3时,如果不小心全部创建了g1,结果和没写错几乎是一样的;
问题版运行结果
g1 = gevent.spawn(test1, 5) g2 = gevent.spawn(test2, 5) g3 = gevent.spawn(test3, 5) g1.join() g1.join() g1.join() ---test1--- <greenlet at 0x17d8ef12598: test1(5)> 0 ---test2--- <greenlet at 0x17d8ef126a8: test2(5)> 0 ---test3--- <greenlet at 0x17d8ef127b8: test3(5)> 0 ---test1--- <greenlet at 0x17d8ef12598: test1(5)> 1 ---test2--- <greenlet at 0x17d8ef126a8: test2(5)> 1 ---test3--- <greenlet at 0x17d8ef127b8: test3(5)> 1 ---test1--- <greenlet at 0x17d8ef12598: test1(5)> 2 ---test2--- <greenlet at 0x17d8ef126a8: test2(5)> 2 ---test3--- <greenlet at 0x17d8ef127b8: test3(5)> 2 ---test1--- <greenlet at 0x17d8ef12598: test1(5)> 3 ---test2--- <greenlet at 0x17d8ef126a8: test2(5)> 3 ---test3--- <greenlet at 0x17d8ef127b8: test3(5)> 3 ---test1--- <greenlet at 0x17d8ef12598: test1(5)> 4 ---test2--- <greenlet at 0x17d8ef126a8: test2(5)> 4 ---test3--- <greenlet at 0x17d8ef127b8: test3(5)> 4
协程的核心在于利用延时操作去做其他的任务;
给gevent打补丁
当我们使用gevent的时候,如果要延时操作,比如等待网络资源或者time.sleep(),必须要使用 gevent.sleep(),即每处延时操作都需要改成gevent的延时;如果我们想,还是按照原来的写法,并且使用gevent,怎么实现呢?这个实收,我们解疑使用打补丁的方法。只需要给使用gevent的代码添加如下一行代码即可完成打补丁
from gevent import monkey monkey.patch_all()
使用打补丁的方式完成协程的使用
import time import gevent from gevent import monkey monkey.patch_all() def test1(n): for i in range(n): print("---test1---", gevent.getcurrent(), i) time.sleep(0.5) # 在打补丁的情况下等效于 gevent.sleep(0.5) def test2(n): for i in range(n): print("---test2---", gevent.getcurrent(), i) time.sleep(0.5) def test3(n): for i in range(n): print("---test3---", gevent.getcurrent(), i) time.sleep(0.5) g1 = gevent.spawn(test1, 5) g2 = gevent.spawn(test2, 5) g3 = gevent.spawn(test3, 5) g1.join() g2.join() g3.join()
给gevent打补丁,使time.sleep(1)之类的耗时操作等效于gevent.sleep(1);
如果我们有很多函数要调用,那么岂不是得每次都先创建,在join(),gevent提供了一种简便方式;
import time import gevent from gevent import monkey monkey.patch_all() def test1(n): for i in range(n): print("---test1---", gevent.getcurrent(), i) time.sleep(0.5) # 在打补丁的情况下等效于 gevent.sleep(0.5) def test2(n): for i in range(n): print("---test2---", gevent.getcurrent(), i) time.sleep(0.5) def test3(n): for i in range(n): print("---test3---", gevent.getcurrent(), i) time.sleep(0.5) gevent.joinall([ gevent.spawn(test1, 5), # 括号内前面的是函数名,后面的是传参 gevent.spawn(test2, 5), gevent.spawn(test3, 5), ])
协程使用小案例-图片下载器
import urllib.request import gevent from gevent import monkey monkey.patch_all() def img_download(img_name, img_url): req = urllib.request.urlopen(img_url) data = req.read() with open("images/"+img_name, "wb") as f: f.write(data) def main(): gevent.joinall([ gevent.spawn(img_download, "1.jpg", "https://rpic.douyucdn.cn/live-cover/appcovers/2019/05/13/6940298_20190513113912_small.jpg"), gevent.spawn(img_download, "2.jpg", "https://rpic.douyucdn.cn/asrpic/190513/2077143_6233919_0d516_2_1818.jpg"), gevent.spawn(img_download, "3.jpg", "https://rpic.douyucdn.cn/live-cover/appcovers/2018/11/24/1771605_20181124143723_small.jpg") ]) if __name__ == "__main__": main()
区别
迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
推荐原来看过的一篇博客:一文彻底搞懂python可迭代(iterable)、迭代器(iterator)和生成器(generator)的概念 ,不过和本文关系不大,哈哈~
在了解迭代器之前,我们来认识两个单词
iterable 可迭代的/可迭代/可迭代对象 iterator 迭代器
迭代器引入-for循环
in [1]: for i in [11,22,33]: ...: print(i) 11 22 33 in [2]: for i in "hhh": ...: print(i) h h h in [3]: for i in 10: ...: print(i) ...: --------------------------------------------------------------------------- typeerror traceback (most recent call last) <ipython-input-3-309758a01ba4> in <module>() ----> 1 for i in 10: 2 print(i) 3 typeerror: 'int' object is not iterable # “int”对象不可迭代
使用for循环时,in后面的数据类型是可迭代的 才可以使用for循环,例如元组,列表,字符串等;不可迭代的,例如数字,小数点的;
判断列表是否是可迭代的:
from collections import iterable isinstance([11,22,33], iterable) true
isinstance判断数据类型是否可迭代
in [6]: from collections import iterable in [7]: isinstance([11,22], iterable) out[7]: true in [8]: isinstance((11,22), iterable) out[8]: true in [9]: isinstance(10, iterable) out[9]: false
元组,列表,字符串都是可迭代的;数字,小数不可迭代;
我们把可以通过for...in...这类语句迭代读取一条数据供我们使用的对象称之为可迭代对象(iterable)。
自己定义的一个类,判断能不能用for?
自己创建一个类,满足能用for循环遍历的需求
不可迭代
class classmate(object): """docstring for classmate""" def __init__(self): self.names = list() def add(self, name): self.names.append(name) classmate = classmate() classmate.add("张三") classmate.add("李四") classmate.add("王五") for name in classmate: print(name) # typeerror: 'classmate' object is not iterable
我们分析对可迭代对象进行迭代使用的过程,发现每迭代一次(即在for...in...中每循环一次)都会返回对象中的下一条数据,一直向后读取数据直到迭代了所有数据后结束。那么,在这个过程中就应该有一个“人”去记录每次访问到了第几条数据,以便每次迭代都可以返回下一条数据。我们把这个能帮助我们进行数据迭代的“人”称为迭代器(iterator)。
可迭代对象的本质就是可以向我们提供一个这样的中间“人”即迭代器帮助我们对其进行迭代遍历使用。
可迭代对象通过__iter__方法向我们提供一个迭代器,我们在迭代一个可迭代对象的时候,实际上就是先获取该对象提供的一个迭代器,然后通过这个迭代器来依次获取对象中的每一个数据.
那么也就是说,一个具备了__iter__方法的对象,就是一个可迭代对象。
如果你不理解上面的话,没关系,你只需要知道 “如果想要将自己定义的一个类变为可迭代的,那么只需要在这个类中定义一个 __iter__ 方法即可”。
添加__iter__方法
class classmate(object): """docstring for classmate""" def __init__(self): self.names = list() def add(self, name): self.names.append(name) def __iter__(self): pass classmate = classmate() classmate.add("张三") classmate.add("李四") classmate.add("王五") for name in classmate: print(name) # typeerror: iter() returned non-iterator of type 'nonetype' # iter()返回“nonetype”类型的非迭代器
注意,这个时候的classmate已经是可迭代对象了,可以用isinstance(classmate, iterable)验证;
但如果将__iter__()方法注释掉,就不是可迭代对象了,所以可以验证,要成为可迭代对象的第一步是添加__iter__()方法;
可迭代与迭代器
判断是否可迭代
以下列代码为例
for i in classmate
流程:
自定义使用for循环步骤
for...in...循环的本质
for item in iterable
循环的本质就是先通过iter()函数获取可迭代对象iterable的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,当遇到stopiteration的异常后循环结束。
一个实现了__iter__方法和__next__方法的对象,就是迭代器。
让迭代器可以完整返回所有的数据;
import time from collections.abc import iterable, iterator class classmate(object): def __init__(self): self.names = list() def add(self, name): self.names.append(name) def __iter__(self): return classmateiterable(self) class classmateiterable(object): def __init__(self, obj): self.obj = obj self.num = 0 def __iter__(self): pass def __next__(self): # return self.obj.names[0] try: ret = self.obj.names[self.num] self.num += 1 return ret except indexerror as e: raise stopiteration def main(): classmate = classmate() classmate.add("张三") classmate.add("李四") classmate.add("王五") print("判断classmate是否为可迭代的:", isinstance(classmate, iterable)) classmate_iterator = iter(classmate) print("判断classmate_iterator是否为迭代器:", isinstance(classmate_iterator, iterator)) # 调用一次 __next__ print("classmate_iterator's next:", next(classmate_iterator)) for i in classmate: print(i) time.sleep(1) if __name__ == '__main__': main()
可以看到,现在已经可以实现for循环使用自定义的类了;但在这个代码里我们看到为了实现返回迭代器我们要再定义一个额外的类,这样是比较麻烦的。在这里我们可以进行简化一下,不返回另一个类,而是返回自己这个类,并且在自己类中定义一个 __next__ 方法。简化如下
改进简化迭代器
import time from collections.abc import iterable, iterator class classmate(object): def __init__(self): self.names = list() self.num = 0 def add(self, name): self.names.append(name) def __iter__(self): return self def __next__(self): # return self.obj.names[0] try: ret = self.names[self.num] self.num += 1 return ret except indexerror as e: raise stopiteration def main(): classmate = classmate() classmate.add("张三") classmate.add("李四") classmate.add("王五") for i in classmate: print(i) time.sleep(1) if __name__ == '__main__': main()
迭代器的作用
python3中使用range:
>>> range(10) range(0, 10) >>> ret = range(10) >>> next(ret) traceback (most recent call last): file "<pyshell#3>", line 1, in <module> next(ret) typeerror: 'range' object is not an iterator >>> for i in range(10): print(i) 0 1 2 3 ...
正常实现斐波那契数列
nums = [] a = 0 b = 1 i = 0 while i < 10: nums.append(a) a, b = b, a+b i += 1 for i in nums: print(i)
使用迭代器实现斐波那契数列
class fibonacci(object): def __init__(self, times): self.times = times self.a = 0 self.b = 1 self.current_num = 0 def __iter__(self): return self def __next__(self): if self.current_num < self.times: ret = self.a self.a, self.b = self.b, self.a+self.b self.current_num += 1 return ret else: raise stopiteration fibo = fibonacci(10) for i in fibo: print(i)
什么时候调,什么时候生成。
当我们使用 list() 或者 tuple() 进行类型转换时,使用的其实也是迭代器;
a = (11,22,33) b = list(a)
当我们使用list()将元组转换成列表时,是使用了迭代器的原理,先定义一个空列表,用迭代器 通过 __next__ 从元组中取第一个值,添加到空列表中,再依次从元组取值,添加入列表,直到元组中没有值了,主动抛出迭代停止异常;
同理,将列表转换成元组也是如此;
迭代器:用来节省内存空间而且还知道将来怎么生成数据的方式;
生成器:一种特殊的迭代器;
生成器方式:
实现生成器方式1
in [15]: l = [ x*2 for x in range(5)] in [16]: l out[16]: [0, 2, 4, 6, 8] in [17]: g = ( x*2 for x in range(5)) in [18]: g out[18]: <generator object <genexpr> at 0x7f626c132db0> in [19]: next(g) out[19]: 0 in [20]: next(g) out[20]: 2
实现生成器方式2
使用yield的生成器
def fibonacci(n): a, b = 0, 1 count_num = 0 while count_num < n: # 如果函数中有一个yield语句,那么这个就不再是函数,而是一个生成器的模板 yield a a, b = b, a+b count_num += 1 # 如果在调用时发现这个函数中有yield,那么此时,不是调用函数,而是创建一个生成器对象 fb = fibonacci(5) print("使用for循环遍历生成器中的所有数字".center(40, "-")) for i in fb: print(i)
生成器执行流程:当第一次调用for/next执行时,会从生成器的第一行开始依次向下执行,直到在循环中碰见yield,就会返回yield后面的变量/字符;然后第二次调用for/next时,就会从上次的yield后面的代码继续执行,直到在循环中再次碰到yield,返回;依次往下,直到没有了数据。
可以使用 for i in 生成器对象 来遍历生成器中的数据,也可以用 next(生成器对象) 来一个一个获取生成器中的值;
使用next获取生成器中的值
def fibonacci(n): a, b = 0, 1 count_num = 0 while count_num < n: # 如果函数中有一个yield语句,那么这个就不再是函数,而是一个生成器的模板 yield a a, b = b, a+b count_num += 1 # 如果在调用时发现这个函数中有yield,那么此时,不是调用函数,而是创建一个生成器对象 fb = fibonacci(5) print("使用next依次生成三次数字".center(40, "-")) print(next(fb)) print(next(fb)) print(next(fb)) print("使用for循环遍历剩余的数字".center(40, "-")) for i in fb: print(i)
可以重复创建多个生成器,多个生成器之间互不干扰;
如果在生成器中有return值,可以在生成器结束后用 出错的结果.value 来进行接收;
def fibonacci(n): a, b = 0, 1 count_num = 0 while count_num < n: # 如果函数中有一个yield语句,那么这个就不再是函数,而是一个生成器的模板 yield a a, b = b, a+b count_num += 1 return "okhaha" # 如果在调用时发现这个函数中有yield,那么此时,不是调用函数,而是创建一个生成器对象 fb = fibonacci(5) while 1: try: result = next(fb) print(result) except exception as e: print(e.value) break
除了使用next来启动生成器之外,还可以使用send来启动生成器;
def fibonacci(n): a, b = 0, 1 count_num = 0 while count_num < n: ret = yield a print("ret:", ret) a, b = b, a+b count_num += 1 fb = fibonacci(5) print(next(fb)) print(fb.send("haha")) print(next(fb)) # 0 # ret: haha # 1 # ret: none # 1
我们可以理解为,第一次使用next,先执行等号右边的代码,就将yield a返回给了next(fb);然后下次调用send时,执行等号左边的,将send的传值赋值给ret,再执行后续代码;
或者我们可以理解 ret = yield a 为两步 ===>1.yield a; 2.ret = arg;其中的arg表示send的传值,如果不传值,默认为none,所以当next在send后面调用时,就默认传了none;
注意,一般不将send用作第一次唤醒生成器,如果一定要使用send第一次唤醒,要send(none);
生成器-小总结
生成器特点:
使用yield完成多任务
进程之间切换任务,占用的资源很大,创建进程,释放进程需要浪费大量的时间,进程的效率没有线程高,比线程占用资源更少的是协程;
使用yield完成多任务
import time def task_1(): while 1: print("---1---") time.sleep(0.5) yield def task_2(): while 1: print("---2---") time.sleep(0.5) yield def main(): t1 = task_1() t2 = task_2() while 1: next(t1) next(t2) if __name__ == "__main__": main()
是假的多任务,属于并发;
如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复
Python 实现将numpy中的nan和inf,nan替换成对应的均值
python爬虫把url链接编码成gbk2312格式过程解析
网友评论