博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
多任务--协程
阅读量:4562 次
发布时间:2019-06-08

本文共 14609 字,大约阅读时间需要 48 分钟。

 

迭代器:

常用的可以迭代的类型:

1 from collections import Iterable2 print(issubclass(int,Iterable))    #False3 print(issubclass(float,Iterable))  #False4 print(issubclass(list,Iterable))   #True5 print(issubclass(tuple,Iterable))  #True6 print(issubclass(dict,Iterable))   #True7 print(issubclass(str,Iterable))    #True8 print(issubclass(set,Iterable))    #True

 

下面尝试自己写个可以迭代的类出来:

1 class ClassMate: 2     def __init__(self): 3         self.names = list() 4      5     def add(self,name): 6         self.names.append(name) 7  8 if __name__ == '__main__': 9     classmate = ClassMate()10     classmate.add("tom")11     classmate.add("jane")12     classmate.add("egon")13     14     for name in classmate:   #报错,此时是不可以迭代的15         print(name)   此时的报错: TypeError: 'ClassMate' object is not iterable

继续:

1 class ClassMate: 2     def __init__(self): 3         self.names = list() 4  5     def add(self,name): 6         self.names.append(name) 7  8     def __iter__(self): 9         pass10 11 if __name__ == '__main__':12     classmate = ClassMate()13     classmate.add("tom")14     classmate.add("jane")15     classmate.add("egon")16 17     for name in classmate: #此时是不可以迭代的18         print(name)19     20     #此时的报错是:TypeError: iter() returned non-iterator of type 'NoneType'

但是,此时它已经是个可迭代的对象了,使用如下代码验证:

print(issubclass(ClassMate,Iterable))

所以:

 

但是,还是用不了for 循环。

 

继续看:

只要使__iter__() 方法返回一个有iter 和next 方法的对象就行了!

for 循环的执行过程,

 

for 循环得到的是返回的对象里的__next__() 返回的值!

1 from collections import Iterable,Iterator 2  3 class ClassMate: 4     def __init__(self): 5         self.names = list() 6  7     def add(self,name): 8         self.names.append(name) 9 10     def __iter__(self):11         # pass  #必须要返回一个具有 iter 和 next 方法的对象12         return MyIterator()13 14 15 class MyIterator:16     def __iter__(self):17         pass18     def __next__(self):19         pass20 21 22 if __name__ == '__main__':23     classmate = ClassMate()24     classmate.add("tom")25     classmate.add("jane")26     classmate.add("egon")27 28     #判断 classmate 是否是可迭代对象29     # print(isinstance(classmate,Iterable))  #只要是有对象iter() 方法就行,就是可迭代对象30 31 32     myiterator = iter(classmate) #它返回的是MyIterator 的对象 ,它是个迭代器33     #判断myiterator 是否是迭代器34     # print(isinstance(myiterator,Iterator)) true  #迭代器要满足iter() 和next() 都有

理论上,此时已经可以运行,但是,迭代器中的next 中还需要一些处理:

1 from collections import Iterable,Iterator 2 import time 3  4 class ClassMate: 5     def __init__(self): 6         self.names = list() 7  8     def add(self,name): 9         self.names.append(name)10 11     def __iter__(self):12         # pass  #必须要返回一个具有 iter 和 next 方法的对象13         return MyIterator()14 15 16 class MyIterator:17     def __iter__(self):18         pass19     def __next__(self):20         return 1121 22 23 24 if __name__ == '__main__':25     classmate = ClassMate()26     classmate.add("tom")27     classmate.add("jane")28     classmate.add("egon")29 30     for name in classmate:31         print(name)32         time.sleep(1)  #这时的输出是 每1s 打印一遍1133

每秒打印11  ,也验证了上面的说法,for name in classmate 时,

首先判断classmate 是否可迭代(__iter__())

继续,判断classmate 中的__iter__() 的返回值是否是个迭代器(对象有 __iter__() __next__())

最后,得到的name 就是 迭代器对象中的__next__() 方法的返回值 !

 

继续改进:

1 from collections import Iterable,Iterator 2 import time 3  4 class ClassMate: 5     def __init__(self): 6         self.names = list() 7  8     def add(self,name): 9         self.names.append(name)10 11     def __iter__(self):12         # pass  #必须要返回一个具有 iter 和 next 方法的对象13         return MyIterator(self.names)14 15 16 class MyIterator:17     def __init__(self,args):18         self.args = args19         self.current_num = 020     def __iter__(self):21         pass22     def __next__(self):23         if self.current_num

不过这时的问题是:有个多余的类,所以我们考虑在一个类里就搞定事情:

最终如下:

from collections import Iterable,Iteratorimport timeclass ClassMate:    def __init__(self):        self.names = list()        self.current_num = 0    def add(self,name):        self.names.append(name)    def __iter__(self):        # pass  #必须要返回一个具有 iter 和 next 方法的对象        return self   #返回的是个具有 next 和iter 的迭代器    def __next__(self):        if self.current_num

迭代器的应用:

迭代器的优点:

占用极小的内存空间,它存储的是生成数据的方式,而不是真实的数据本身!

斐波那契数列案例:

第一种:用列表放这个数列:

1 nums = list() 2 a = 0 3 b = 1 4  5 for _ in range(10): 6     nums.append(a) 7     a,b = b,a+b 8  9 for num in nums:10     print(num)

第二种:用迭代器放这个数列:(占用很小的内存)

1 class Fib: 2     def __init__(self,all_num): 3         self.current_num = 0 4         self.all_num = all_num 5         self.a = 0 6         self.b = 1 7  8     def __iter__(self): 9         return self10 11     def __next__(self):12         if self.current_num 

并不是只有for循环能接收可迭代对象

除了for循环能接收可迭代对象,list、tuple等也能接收。

它们并不是进行简单的类型转换,

例如,列表->  tuple   

它先是创建一个元组,然后利用迭代器取出每个值,然后放入新元组中!

1 def test():2     li = list([1,2,3,4])3     tu = tuple(li)4

 

生成器:

利用迭代器,我们可以在每次迭代获取数据(通过next()方法)时按照特定的规律进行生成。但是我们在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合next()函数进行迭代使用,我们可以采用更简便的语法,即生成器(generator)。生成器是一类特殊的迭代器。

创建生成器方法1:

第一种方法很简单,只要把一个列表生成式的 [ ] 改成 ( )

创建生成器方法2:

generator非常强大。如果推算的算法比较复杂,用类似列表生成式的 for 循环无法实现的时候,还可以用函数来实现。

使用函数生成器实现斐波那契数列:

1 def fib(sum_num): 2     a,b = 0,1 3     current_num  = 0 4     while current_num 
12 13 # for i in ret:14 # print(i)15 16 # print(ret.__next__()) #next(ret)17 # print(ret.__next__()) #next(ret)

生成器扩展:

此时的函数可以直接当做是个模板(像类一样),

可以创建多个生成器对象:

1 def fib(sum_num): 2     a,b = 0,1 3     current_num  = 0 4     while current_num 

捕获StopIteration 异常以及获取return 返回的内容:

1 def fib(sum_num): 2     a, b = 0, 1 3     current_num = 0 4     while current_num < sum_num: 5         yield a  # 如果一个函数中有了yield 语句,那么函数就变成了生成器 6         a, b = b, a + b 7         current_num += 1 8     return "ok..." 9 10 11 if __name__ == '__main__':12     obj = fib(10)13 14     while True:15         try:16             ret = obj.__next__()17             print(ret)18         except Exception as e:19             print(e.value)  #通过捕获异常来获取迭代器的返回的内容。20             break21     '''22     输出:23         024         125         126         227         328         529         830         1331         2132         3433         ok...34     '''

 

生成器--之send方式:

上面的是用next/__next__()  来产生下个值,用send也可以 ,send 可以向里面传入参数。

1 def fib(sum_num): 2     a, b = 0, 1 3     current_num = 0 4     while current_num < sum_num: 5         test = yield a  # 如果一个函数中有了yield 语句,那么函数就变成了生成器 6         print(test) 7         a, b = b, a + b 8         current_num += 1 9     return "ok..."10 11 12 if __name__ == '__main__':13     obj = fib(10)14 15     ret = obj.__next__()16     print(ret)17 18     ret = obj.send("hahahaha")19     print(ret)

 

注意:如果生成器  第一次启动时,如果用send() 的话,send 只能传入None 参数,不能传入非None 参数,从第二次开始,就可以随便传入了。

一般来说第一次都是用next ,需要向里面传值的时候才会使用send  .

注:c.next()等价c.send(None)

 

yield 和return 的区别:

yield 可以暂停,然后后面继续执行它。 return 是直接结束!

 

协程-yield(使用yield 完成多任务):

1 import time 2 def task_1(): 3     while True: 4         print("====1===") 5         time.sleep(0.1) 6         yield 7  8 def task_2(): 9     while True:10         print("====2===")11         time.sleep(0.1)12         yield13 14 if __name__ == '__main__':15     t1 = task_1()  #得到是对象16     t2 = task_2()17     while True:18         t1.__next__()19         t2.__next__()

不过,这个多任务是假的多任务,它是并发,而不是真正的并行! 

它其实是一个进程中的一个线程在工作。

 

协程-greenlet 和 gevent :

greenlet :  

from greenlet import greenlet  # 使用greenlet 中的switch 就可以完成切换任务。import timedef test01():    while True:        print("====1====")        grlet2.switch()        time.sleep(0.1)def test02():    while True:        print("====2====")        grlet1.switch()        time.sleep(0.1)if __name__ == '__main__':    grlet1 = greenlet(test01)    grlet2 = greenlet(test02)    grlet1.switch()   #它的效果和上面的效果一样!

 

gevent:

greenlet 是对yield 的封装, gevent 是对greenlet 的封装!

 

它还是用的最多的,主要是它遇到阻塞(time.sleep())切换!

greenlet已经实现了协程,但是这个还的人工切换,是不是觉得太麻烦了,不要捉急,python还有一个比greenlet更强大的并且能够自动切换任务的模块gevent

其原理是当一个greenlet遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。

由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO

 

没有阻塞的时候(不切换):

1 import gevent 2  3  4 def f1(n): 5     for i in range(n): 6         print(gevent.getcurrent(),i) 7  8 def f2(n): 9     for i in range(n):10         print(gevent.getcurrent(),i)11 12 def f3(n):13     for i in range(n):14         print(gevent.getcurrent(),i)15 16 if __name__ == '__main__':17     print("=======1=========")18     g1 = gevent.spawn(f1,5)    #此时不会执行f119     print("=======2=========")20     g2 = gevent.spawn(f2,5)21     print("=======3=========")22     g3 = gevent.spawn(f3,5)23     print("=======4=========")24 25     g1.join()26     g2.join()27     g3.join()28     '''29     输出:30         =======1=========31         =======2=========32         =======3=========33         =======4=========34         
035
136
237
338
439
040
141
242
343
444
045
146
247
348
449 '''

有阻塞的时候(切换):

1 import gevent 2 import time 3  4 def f1(n): 5     for i in range(n): 6         print(gevent.getcurrent(),i) 7         # time.sleep(0.5) #这种阻塞不行,需要专门的gevent.sleep() 8         gevent.sleep(0.5) 9 10 def f2(n):11     for i in range(n):12         print(gevent.getcurrent(),i)13         gevent.sleep(0.5)14 15 def f3(n):16     for i in range(n):17         print(gevent.getcurrent(),i)18         gevent.sleep(0.5)19 20 if __name__ == '__main__':21     print("=======1=========")22     g1 = gevent.spawn(f1,5)    #此时不会执行f123     print("=======2=========")24     g2 = gevent.spawn(f2,5)25     print("=======3=========")26     g3 = gevent.spawn(f3,5)27     print("=======4=========")28 29     g1.join()30     g2.join()31     g3.join()32     '''33     输出:34         =======1=========35         =======2=========36         =======3=========37         =======4=========38         
039
040
041
142
143
144
245
246
247
348
349
350
451
452
453 '''

 

它主要的思想是利用当一个任务在浪费时间的时候,将这个时间给利用上!

这就是协程!

协程依赖线程!协程使用的资源是最小的!

注:不仅仅time.sleep() 阻塞要用gevent 中的专门函数,对于网络编程中的recv   accept 等阻塞也要换!但是如果对于已经有现成项目的代码,已经用了很多的time.sleep() ,如果每个都要改就麻烦,

 

下面的方法可以不用改为gevent ():

打个补丁:

monkey.patch_all()   # 要导入  from gevent import monkey  

from gevent import monkeyimport geventimport timemonkey.patch_all()def f1(n):    for i in range(n):        print(gevent.getcurrent(),i)        time.sleep(0.5)def f2(n):    for i in range(n):        print(gevent.getcurrent(),i)        time.sleep(0.5)def f3(n):    for i in range(n):        print(gevent.getcurrent(),i)        time.sleep(0.5)if __name__ == '__main__':    print("=======1=========")    g1 = gevent.spawn(f1,5)    #此时不会执行f1    print("=======2=========")    g2 = gevent.spawn(f2,5)    print("=======3=========")    g3 = gevent.spawn(f3,5)    print("=======4=========")    g1.join()    g2.join()    g3.join()    '''    输出:        =======1=========        =======2=========        =======3=========        =======4=========        
0
0
0
1
1
1
2
2
2
3
3
3
4
4
4 '''

 

gevent.joinall([ ])  的写法:

gevent 的标准用法模板:

1 from gevent import monkey 2 import gevent 3 import time 4  5 monkey.patch_all()  #  它会自动的将所有代码中的 要阻塞的函数换成gevent 专门的函数 6  7 def f1(n): 8     for i in range(n): 9         print(gevent.getcurrent(),i)10         time.sleep(0.5)11 12 def f2(n):13     for i in range(n):14         print(gevent.getcurrent(),i)15         time.sleep(0.5)16 17 def f3(n):18     for i in range(n):19         print(gevent.getcurrent(),i)20         time.sleep(0.5)21 22 23 if __name__ == '__main__':24     gevent.joinall([   #joinall 的写法,将所有的对象放到一个数组中!  方便25         gevent.spawn(f1, 5),26         gevent.spawn(f2, 5),27         gevent.spawn(f3, 5)28     ])29 30     '''31     输出:32         
033
034
035
136
137
138
239
240
241
342
343
344
445
446
447 '''

 

 

 

进程,线程,协程区别:

通俗描述

  • 有一个老板想要开个工厂进行生产某件商品(例如剪子)
  • 他需要花一些财力物力制作一条生产线,这个生产线上有很多的器件以及材料这些所有的 为了能够生产剪子而准备的资源称之为:进程
  • 只有生产线是不能够进行生产的,所以老板的找个工人来进行生产,这个工人能够利用这些材料最终一步步的将剪子做出来,这个来做事情的工人称之为:线程
  • 这个老板为了提高生产率,想到3种办法:
    1. 在这条生产线上多招些工人,一起来做剪子,这样效率是成倍増长,即单进程 多线程方式
    2. 老板发现这条生产线上的工人不是越多越好,因为一条生产线的资源以及材料毕竟有限,所以老板又花了些财力物力购置了另外一条生产线,然后再招些工人这样效率又再一步提高了,即多进程 多线程方式
    3. 老板发现,现在已经有了很多条生产线,并且每条生产线上已经有很多工人了(即程序是多进程的,每个进程中又有多个线程),为了再次提高效率,老板想了个损招,规定:如果某个员工在上班时临时没事或者再等待某些条件(比如等待另一个工人生产完谋道工序 之后他才能再次工作) ,那么这个员工就利用这个时间去做其它的事情,那么也就是说:如果一个线程等待某些条件,可以充分利用这个时间去做其它事情,其实这就是:协程方式

简单总结

  1. 进程是资源分配的单位
  2. 线程是操作系统调度的单位
  3. 进程切换需要的资源很最大,效率很低
  4. 线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下)
  5. 协程切换任务资源很小,效率高
  6. 多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中 所以是并发

效率最高的是协程!

 

案例:并发下载器:

通过程序上网,可以通过 import  urllib.request ,使用里面的urlopen() 来打开url 并获取结果! 

一次爬取一张图片:

1 import urllib.request 2  3 def main(): 4     req = urllib.request.urlopen("https://img02.sogoucdn.com/v2/thumb/resize/w/120/h/135/zi/on/iw/90.0/ih/101.0?t=2&url=http%3A%2F%2Fpic.baike.soso.com%2Fugc%2Fbaikepic2%2F19951%2Fcut-20190523135122-1670915825_jpg_298_373_11703.jpg%2F300&appid=200524&referer=http%3A%2F%2Fbaike.sogou.com%2Fv6234.htm%3FfromTitle%3D%25E7%2599%25BE%25E5%25BA%25A6") 5  6     img_content = req.read() 7     with open("e:/1.jpg","wb") as f: 8         f.write(img_content) 9 10 if __name__ == '__main__':11     main()

 

因为网络下载是个阻塞的过程,所以可以使用gevent 利用协程来下载!

1 import urllib.request 2 import gevent 3 from gevent import monkey 4 import time 5  6 monkey.patch_all() 7  8  9 10 def downloader(img_url):11     req = urllib.request.urlopen(img_url)12 13     img_content = req.read()14     timestamp =time.time()   #时间戳 15     with open("e:/{}.jpg".format(timestamp),"wb") as f:16         f.write(img_content)17 18 def main():19     gevent.joinall([20         gevent.spawn(downloader,"http://img0.dili360.com//ga/M02/49/A2/wKgBy1nPDXGAWgvpAADIK-c2-PQ056.jpg"),21         gevent.spawn(downloader, "http://img0.dili360.com/ga/M01/02/64/wKgBy1Q22liAYjlPAA0ov-3Wnvs953.jpg@!rw9")22     ])23 24 25 if __name__ == '__main__':26     main()

补:

这上面说的是迭代器,生成器,以后还会说装饰器,先看一个装饰器的小例子:

import timedef decorator(func):    def wrapper(name):        t1=time.perf_counter()        func(name)        time.sleep(1)        print("总时间为:",time.perf_counter() - t1)    return wrapper@decoratordef test(name):    print("Hello World!",name)if __name__ == '__main__':    test("tom")
View Code

它们三个(迭代器,生成器和装饰器)是Python 高级语法中重要的内容!

 

转载于:https://www.cnblogs.com/zach0812/p/11443985.html

你可能感兴趣的文章
Chales常用操作
查看>>
C++ 运算符重载<<
查看>>
windows镜像
查看>>
Flask 模板语法
查看>>
ZOJ FatMouse' Trade 贪心
查看>>
音乐播放器
查看>>
SQL COOKBOOK (Ch.1-10)
查看>>
创建数组
查看>>
dict使用
查看>>
[转] 移动平台Html5的viewport使用经验
查看>>
ASP.NET MVC的帮助类HtmlHelper和UrlHelper
查看>>
《Python数据科学手册》第五章机器学习的笔记
查看>>
ubuntu16.04 配置爬虫环境
查看>>
Centos7,PHP7安装swoole
查看>>
02_ListActive中响应事件 并LogCat输出
查看>>
doubleclick adx note
查看>>
Celery框架
查看>>
[c#]asp.net开发微信公众平台(4)关注事件、用户记录、回复文本消息
查看>>
[转载,感觉写的非常详细]DUBBO配置方式详解
查看>>
linux Valgrind使用说明-内存泄漏
查看>>