迭代器:
常用的可以迭代的类型:
1 from collections import Iterable 2 print(issubclass(int,Iterable)) #False 3 print(issubclass(float,Iterable)) #False 4 print(issubclass(list,Iterable)) #True 5 print(issubclass(tuple,Iterable)) #True 6 print(issubclass(dict,Iterable)) #True 7 print(issubclass(str,Iterable)) #True 8 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 pass 10 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 pass 18 def __next__(self): 19 pass 20 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 pass 19 def __next__(self): 20 return 11 21 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 打印一遍11 33
每秒打印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 = args 19 self.current_num = 0 20 def __iter__(self): 21 pass 22 def __next__(self): 23 if self.current_num<len(self.args): 24 ret = self.args[self.current_num] 25 self.current_num +=1 26 return ret 27 else: 28 raise StopIteration #结束for 循环 29 30 if __name__ == '__main__': 31 classmate = ClassMate() 32 classmate.add("tom") 33 classmate.add("jane") 34 classmate.add("egon") 35 36 for name in classmate: 37 print(name) 38 time.sleep(1)
不过这时的问题是:有个多余的类,所以我们考虑在一个类里就搞定事情:
最终如下:
from collections import Iterable,Iterator import time class 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<len(self.names): ret = self.names[self.current_num] self.current_num +=1 return ret #它就是for 循环的name else: raise StopIteration #for in 结构会默认处理这个异常 if __name__ == '__main__': classmate = ClassMate() classmate.add("tom") classmate.add("jane") classmate.add("egon") for name in classmate: time.sleep(1) print(name) ''' 这也说明了一点: 一个可以迭代的对象 不一定 是个迭代器 一个迭代器 一定 是可迭代的对象 '''
迭代器的应用:
迭代器的优点:
占用极小的内存空间,它存储的是生成数据的方式,而不是真实的数据本身!
斐波那契数列案例:
第一种:用列表放这个数列:
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 self 10 11 def __next__(self): 12 if self.current_num <self.all_num: 13 ret = self.a 14 15 self.a ,self.b = self.b ,self.a +self.b 16 17 self.current_num +=1 18 return ret 19 else: 20 raise StopIteration 21 if __name__ == '__main__': 22 fib =Fib(10) 23 24 for i in fib: 25 print(i)
并不是只有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 <sum_num: 5 yield a #如果一个函数中有了yield 语句,那么函数就变成了生成器 6 a,b = b,a+b 7 current_num += 1 8 9 if __name__ == '__main__': 10 ret = fib(10) 11 print(ret) #<generator object fib at 0x0000027E4705E4C0> 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 <sum_num: 5 yield a #如果一个函数中有了yield 语句,那么函数就变成了生成器 6 a,b = b,a+b 7 current_num += 1 8 9 if __name__ == '__main__': 10 ret = fib(10) 11 ret2 = fib(10) 12 13 print(ret.__next__()) 14 print(ret2.__next__())
捕获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 break 21 ''' 22 输出: 23 0 24 1 25 1 26 2 27 3 28 5 29 8 30 13 31 21 32 34 33 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 yield 13 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 time def 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) #此时不会执行f1 19 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 <Greenlet at 0x36f0c90: f1(5)> 0 35 <Greenlet at 0x36f0c90: f1(5)> 1 36 <Greenlet at 0x36f0c90: f1(5)> 2 37 <Greenlet at 0x36f0c90: f1(5)> 3 38 <Greenlet at 0x36f0c90: f1(5)> 4 39 <Greenlet at 0x397c270: f2(5)> 0 40 <Greenlet at 0x397c270: f2(5)> 1 41 <Greenlet at 0x397c270: f2(5)> 2 42 <Greenlet at 0x397c270: f2(5)> 3 43 <Greenlet at 0x397c270: f2(5)> 4 44 <Greenlet at 0x397c300: f3(5)> 0 45 <Greenlet at 0x397c300: f3(5)> 1 46 <Greenlet at 0x397c300: f3(5)> 2 47 <Greenlet at 0x397c300: f3(5)> 3 48 <Greenlet at 0x397c300: f3(5)> 4 49 '''
有阻塞的时候(切换):
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) #此时不会执行f1 23 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 <Greenlet at 0x3bafdb0: f1(5)> 0 39 <Greenlet at 0x3cea270: f2(5)> 0 40 <Greenlet at 0x3cea300: f3(5)> 0 41 <Greenlet at 0x3bafdb0: f1(5)> 1 42 <Greenlet at 0x3cea270: f2(5)> 1 43 <Greenlet at 0x3cea300: f3(5)> 1 44 <Greenlet at 0x3bafdb0: f1(5)> 2 45 <Greenlet at 0x3cea270: f2(5)> 2 46 <Greenlet at 0x3cea300: f3(5)> 2 47 <Greenlet at 0x3bafdb0: f1(5)> 3 48 <Greenlet at 0x3cea270: f2(5)> 3 49 <Greenlet at 0x3cea300: f3(5)> 3 50 <Greenlet at 0x3bafdb0: f1(5)> 4 51 <Greenlet at 0x3cea270: f2(5)> 4 52 <Greenlet at 0x3cea300: f3(5)> 4 53 '''
它主要的思想是利用当一个任务在浪费时间的时候,将这个时间给利用上!
这就是协程!
协程依赖线程!协程使用的资源是最小的!
注:不仅仅time.sleep() 阻塞要用gevent 中的专门函数,对于网络编程中的recv accept 等阻塞也要换!但是如果对于已经有现成项目的代码,已经用了很多的time.sleep() ,如果每个都要改就麻烦,
下面的方法可以不用改为gevent ():
打个补丁:
monkey.patch_all() # 要导入 from gevent import monkey
from gevent import monkey import gevent import time monkey.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========= <Greenlet at 0x3bafdb0: f1(5)> 0 <Greenlet at 0x3cea270: f2(5)> 0 <Greenlet at 0x3cea300: f3(5)> 0 <Greenlet at 0x3bafdb0: f1(5)> 1 <Greenlet at 0x3cea270: f2(5)> 1 <Greenlet at 0x3cea300: f3(5)> 1 <Greenlet at 0x3bafdb0: f1(5)> 2 <Greenlet at 0x3cea270: f2(5)> 2 <Greenlet at 0x3cea300: f3(5)> 2 <Greenlet at 0x3bafdb0: f1(5)> 3 <Greenlet at 0x3cea270: f2(5)> 3 <Greenlet at 0x3cea300: f3(5)> 3 <Greenlet at 0x3bafdb0: f1(5)> 4 <Greenlet at 0x3cea270: f2(5)> 4 <Greenlet at 0x3cea300: f3(5)> 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 <Greenlet at 0x3bafdb0: f1(5)> 0 33 <Greenlet at 0x3cea270: f2(5)> 0 34 <Greenlet at 0x3cea300: f3(5)> 0 35 <Greenlet at 0x3bafdb0: f1(5)> 1 36 <Greenlet at 0x3cea270: f2(5)> 1 37 <Greenlet at 0x3cea300: f3(5)> 1 38 <Greenlet at 0x3bafdb0: f1(5)> 2 39 <Greenlet at 0x3cea270: f2(5)> 2 40 <Greenlet at 0x3cea300: f3(5)> 2 41 <Greenlet at 0x3bafdb0: f1(5)> 3 42 <Greenlet at 0x3cea270: f2(5)> 3 43 <Greenlet at 0x3cea300: f3(5)> 3 44 <Greenlet at 0x3bafdb0: f1(5)> 4 45 <Greenlet at 0x3cea270: f2(5)> 4 46 <Greenlet at 0x3cea300: f3(5)> 4 47 '''
进程,线程,协程区别:
通俗描述
- 有一个老板想要开个工厂进行生产某件商品(例如剪子)
- 他需要花一些财力物力制作一条生产线,这个生产线上有很多的器件以及材料这些所有的 为了能够生产剪子而准备的资源称之为:进程
- 只有生产线是不能够进行生产的,所以老板的找个工人来进行生产,这个工人能够利用这些材料最终一步步的将剪子做出来,这个来做事情的工人称之为:线程
- 这个老板为了提高生产率,想到3种办法:
- 在这条生产线上多招些工人,一起来做剪子,这样效率是成倍増长,即单进程 多线程方式
- 老板发现这条生产线上的工人不是越多越好,因为一条生产线的资源以及材料毕竟有限,所以老板又花了些财力物力购置了另外一条生产线,然后再招些工人这样效率又再一步提高了,即多进程 多线程方式
- 老板发现,现在已经有了很多条生产线,并且每条生产线上已经有很多工人了(即程序是多进程的,每个进程中又有多个线程),为了再次提高效率,老板想了个损招,规定:如果某个员工在上班时临时没事或者再等待某些条件(比如等待另一个工人生产完谋道工序 之后他才能再次工作) ,那么这个员工就利用这个时间去做其它的事情,那么也就是说:如果一个线程等待某些条件,可以充分利用这个时间去做其它事情,其实这就是:协程方式
简单总结
- 进程是资源分配的单位
- 线程是操作系统调度的单位
- 进程切换需要的资源很最大,效率很低
- 线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下)
- 协程切换任务资源很小,效率高
- 多进程、多线程根据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 time def decorator(func): def wrapper(name): t1=time.perf_counter() func(name) time.sleep(1) print("总时间为:",time.perf_counter() - t1) return wrapper @decorator def test(name): print("Hello World!",name) if __name__ == '__main__': test("tom")
它们三个(迭代器,生成器和装饰器)是Python 高级语法中重要的内容!