网络编程(5) - 多任务-协程

111 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

5.1,迭代器

迭代是访问集合元素的一种方式,迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有元素被访问结束,迭代器只往前不往后

  1. 可迭代对象

    我们知道对list,tuple,str等数据类型的数据使用for...in...的循环语法能从其中依次拿到数据进行使用,我们把这样的过程叫做遍历

    但是,是否所有的数据类型都能够被我们放到for...in...里面使用,给我们进行迭代?

    In [1]: for i in 100: 
       ...:     print(i) 
    ...:                                                                               
    Traceback (most recent call last)
    ----> 1 for i in 100:
          2     print(i)
    
    TypeError: 'int' object is not iterable
        
    # int整数不是iterable,即int整数不是可迭代的
    
    # 我们可以自己定义一个容器MyList用来存放数据,可以通过add方法向其中添加数据
    

    查看一个对象是否是可迭代对象

    1. isinstance(对象, Iterable) 使用内置函数判断类型

      from collections import Iterable   
      
      isinstance([1, 2, 3, 4], Iterable)                                                  True
      
      isinstance('dwadadwa', Iterable)                                                   True
      
      isinstance(123, Iterable)                                                           False
      
    2. dir(对象) 查看一个对象是否实现了 __iter__ 这个魔法方法

      dir([])
      ['__add__','__class__','__contains__',...'__iter__',...'remove','reverse','sort']
      
  2. for循环的实现过程

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SD8oZ6Ya-1592064978000)(assets/.png)]

  3. 实现一个迭代器

     # !/usr/bin/env python
     # _*_ coding:utf-8 _*_
     # author:满怀心 2019/8/5 11:47
     """
     # code is far away from bugs with the god animal protecting
         I love animals. They taste delicious.
                   ┏┓      ┏┓
                 ┏┛┻━━━┛┻┓
                 ┃      ☃      ┃
                 ┃  ┳┛  ┗┳  ┃
                 ┃      ┻      ┃
                 ┗━┓      ┏━┛
                     ┃      ┗━━━┓
                     ┃  神兽保佑    ┣┓
                     ┃ 永无BUG!   ┏┛
                     ┗┓┓┏━┳┓┏┛
                       ┃┫┫  ┃┫┫
                       ┗┻┛  ┗┻┛
     """
     from collections import Iterable, Iterator
     
     
     class Classmate(object):
         def __init__(self):
             self.names = list()
     
         def add(self, name):
             self.names.append(name)
     
         def __iter__(self):
             """如果想让一个对象变成一个可迭代对象,必须要实现__iter__方法"""
             return ClassIterator(self.names)
     
     
     class ClassIterator(object):
         def __init__(self, names):
             self.names = names
             self.index = 0
     
         def __iter__(self):
             pass
     
         def __next__(self):
             if self.index < len(self.names):
                 name = self.names[self.index]
                 self.index += 1
                 return name
             else:
                 raise StopIteration
     
     
     classmate = Classmate()
     classmate.add('one')
     classmate.add('two')
     classmate.add('three')
     for name in classmate:
         print(name)
    
-完整版迭代器
 # !/usr/bin/env python
 # _*_ coding:utf-8 _*_
 # author:满怀心 2019/8/5 11:47
 """
 # code is far away from bugs with the god animal protecting
     I love animals. They taste delicious.
               ┏┓      ┏┓
             ┏┛┻━━━┛┻┓
             ┃      ☃      ┃
             ┃  ┳┛  ┗┳  ┃
             ┃      ┻      ┃
             ┗━┓      ┏━┛
                 ┃      ┗━━━┓
                 ┃  神兽保佑    ┣┓
                 ┃ 永无BUG!   ┏┛
                 ┗┓┓┏━┳┓┏┛
                   ┃┫┫  ┃┫┫
                   ┗┻┛  ┗┻┛
 """
 from collections import Iterable, Iterator
 
 
 class Classmate(object):
     def __init__(self):
         self.names = list()
         self.index = 0
 
     def add(self, name):
         self.names.append(name)
 
     def __iter__(self):
         """如果想让一个对象变成一个可迭代对象,必须要实现__iter__方法"""
         return self
 
     def __next__(self):
         if self.index < len(self.names):
             name = self.names[self.index]
             self.index += 1
             return name
         else:
             raise StopIteration
 
 
 classmate = Classmate()
 classmate.add('one')
 classmate.add('two')
 classmate.add('three')
 
 for name in classmate:
     print(name)

5.2,迭代器的应用(重点)

如果要用到一堆值,有可能100个,10000个,说不定要用到,有两种方法生成

  1. 使用列表 : 保存生成的值(占用大量的内存空间)
  2. 使用迭代器 :保存生成值的方式(占用极小的空间实现)

如果要生成1000万个值呢?是在内存里面生成一个列表把这1000万个值存入,还是要用的时候再生成?

  1. 使用列表方式储存

     nums = list()
     
     a = 0
     b = 1
     i = 0
     
     while i < 10:
         nums.append(a)
         a, b = b, a+b
         i += 1
     
     for num in nums:
         print(num)
    
  2. 使用迭代器生成

     class FeibIterator(object):
         def __init__(self, num):
             self.a = 0
             self.b = 1
             self.num = num
             self.current_num = 0
     
         def __iter__(self):
             """如果想让一个对象变成一个可迭代对象,必须要实现__iter__方法"""
             return self
     
         def __next__(self):
             if self.current_num < self.num:
                 ret = self.a
                 self.a, self.b = self.b, self.a+self.b
                 self.current_num += 1
                 return ret
             else:
                 raise StopIteration
     
     a = FeibIterator(50)
     for i in a:
         print(i)
    

    迭代器的另外用途

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

     a = (1, 2, 3, 4, 5)
     list(a)
     
     b = [1, 2, 3, 4, 5]
     tuple(b)
     
     # 首先,先生成一个新的列表,再去找a的迭代器,把值一个一个迭代出来塞到列表里,最后捕捉异常
    

5.3,生成器

  1. 生成器

    利用迭代器,我们可以每次使用next来取值,大量减少了内存空间,只暂用了生成数值的方法

    但是我们在实现一个迭代器时,关于当前迭代到达的状态需要我们自己记录,进而才能根据当前状态生成下一个数据

    为了达到记录当前的状态,我们采用更加简便的语法,即生成器,生成器是一类特殊的迭代器

  2. 创建生成器方法1

    1. 创建一个生成器,有很多种方法。第一种方法很简单,只要把一个列表推导式的[]改成()

       In [2]: l = [i for i in range(10)]   
       
       In [3]: l   
       Out[3]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
           
       In [4]: L = (i for i in range(10))
           
       In [5]: L                                                                         
       Out[5]: <generator object <genexpr> at 0x7f73740b5620>
      

      创建 L 和 G 的的区别仅在于最外层的 [] 和 (),l 是一个列表,而 L 是一个生成器。我们可以直接打印出列表 L 的每一个元素,而对于生成器G,我们可以按照迭代器的使用方法来使用,即可以通过next()函数,for循环,list()等方法

  3. 创建生成器方法2

    1. 使用yield能够生成生成器,在函数里面使用yield能够暂停函数,等待下次的next或者send方法来再次激活

       def func(num):
       	a = 0
       	while a<num :
       		yield a			
       		a += 1
       # 这个函数使用了yield关键字,这是一个生成器函数,返回的对象是生成器对象
       """
       					1,返回这个函数对象
       	yield一个对象:	  2,暂停这个函数
       					3,等待下次next重新激活函数
       """
       a = hello()
       
       print(a)
       print(type(a))
      
  4. 激活生成器方法

    1. 使用next()或者__next__()激活

       def func(num):
       	a = 0
       	while a<num :
       		yield a			
       		a += 1
       
       a = func()
       next(a)
       a.__next__()
      
    2. 使用 send(传入的数据) 激活,获得的值和next获得的值相同

       def func(): 
           a = 0 
           while True: 
               res =  yield a 	# res接收send发送的值,如果用next调用激活,不会传值,为空
               print('--send 发送的数据 : {}--'.format(res)) 
               a += 1 
               
       a = func()
       num1 = next(a)	# 激活生成器,接收生成器生成的值
       print(num1)
       
       num2 = a.send('hahaha')	# send不能放在开头,不然会报错,hahah的值会传给res
       print(num2)
      

5.4,使用yield完成多任务

抓住yield能够使函数暂停,直到下一次的next来激活,从而实现多任务

 # !/usr/bin/env python
 # _*_ coding:utf-8 _*_
 # author:满怀心 2019/8/5 23:15
 """
 # code is far away from bugs with the god animal protecting
     I love animals. They taste delicious.
               ┏┓      ┏┓
             ┏┛┻━━━┛┻┓
             ┃      ☃      ┃
             ┃  ┳┛  ┗┳  ┃
             ┃      ┻      ┃
             ┗━┓      ┏━┛
                 ┃      ┗━━━┓
                 ┃  神兽保佑    ┣┓
                 ┃ 永无BUG!   ┏┛
                 ┗┓┓┏━┳┓┏┛
                   ┃┫┫  ┃┫┫
                   ┗┻┛  ┗┻┛
 """
 import time
 
 
 def task_1():
     while True:
         print('---1---')
         time.sleep(.1)
         yield
 
 
 def task_2():
     while True:
         print('---2---')
         time.sleep(.1)
         yield
 
 
 def main():
     t1 = task_1()
     t2 = task_2()
     while True:
         next(t1)
         next(t2)
 
 
 if __name__ == '__main__':
     main()

5.5,使用greenlet,gevent完成多任务

  1. 使用greenlet(了解)

     from greenlet import greenlet
     import time
     
     
     def text1():
         while True:
             print('-----A-----')
             gr2.switch()
             time.sleep(0.5)
     
     
     def text2():
         while True:
             print('-----B-----')
             gr1.switch()
             time.sleep(0.5)
     
     
     gr1 = greenlet(text1)
     gr2 = greenlet(text2)
     
     gr1.switch()
    
  2. 使用gevent(并发库)

使用gevent库执行的时候,如果碰到延时,会自动切换,充分利用了延时时间去执行别的

  1. gevent的基本使用

     # !/usr/bin/env python
     # _*_ coding:utf-8 _*_
     # author:满怀心 2019/8/6 12:23
     """
     # code is far away from bugs with the god animal protecting
         I love animals. They taste delicious.
                   ┏┓      ┏┓
                 ┏┛┻━━━┛┻┓
                 ┃      ☃      ┃
                 ┃  ┳┛  ┗┳  ┃
                 ┃      ┻      ┃
                 ┗━┓      ┏━┛
                     ┃      ┗━━━┓
                     ┃  神兽保佑    ┣┓
                     ┃ 永无BUG!   ┏┛
                     ┗┓┓┏━┳┓┏┛
                       ┃┫┫  ┃┫┫
                       ┗┻┛  ┗┻┛
     """
     import gevent
     import time
     from gevent import monkey
     
     def func1(n):
         for i in range(n):
             print(gevent.getcurrent(), i)
             # time.sleep(1)	# 碰到这种延时不会切换,必须是换成gevent里面对应的延时
             gevent.sleep(1) # 主线程遇到sleep()发生延时,gevent自动切换
     
     def func2(n):
         for i in range(n):
             print(gevent.getcurrent(), i)
             gevent.sleep(1)	# 主线程遇到sleep()发生延时,gevent自动切换
     
     def func3(n):
         for i in range(n):
             print(gevent.getcurrent(), i)
             gevent.sleep(1)	# 主线程遇到sleep()发生延时,gevent自动切换
     
     f1 = gevent.spawn(func1, 5)
     f2 = gevent.spawn(func2, 5)
     f3 = gevent.spawn(func3, 5)
     
     f1.join()	# 主线程遇到join()发生延时,gevent自动切换
     f2.join()
     f3.join()
    

    结果:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V36v1Qp1-1592064978003)(assets/.png)]

  2. 使用monkey.patch_all()替换代码里的所有延时,实现协程多任务

     # !/usr/bin/env python
     # _*_ coding:utf-8 _*_
     # author:满怀心 2019/8/6 12:23
     """
     # code is far away from bugs with the god animal protecting
         I love animals. They taste delicious.
                   ┏┓      ┏┓
                 ┏┛┻━━━┛┻┓
                 ┃      ☃      ┃
                 ┃  ┳┛  ┗┳  ┃
                 ┃      ┻      ┃
                 ┗━┓      ┏━┛
                     ┃      ┗━━━┓
                     ┃  神兽保佑    ┣┓
                     ┃ 永无BUG!   ┏┛
                     ┗┓┓┏━┳┓┏┛
                       ┃┫┫  ┃┫┫
                       ┗┻┛  ┗┻┛
     """
     import gevent
     import time
     from gevent import monkey
     
     monkey.patch_all()
     
     def func(work_name):
         for i in range(10):
             print(work_name, i)
             time.sleep(random.random())
     
     
     def main():
         gevent.joinall(
             {
                 gevent.spawn(func, 'work1'),
                 gevent.spawn(func, 'work2'),
                 gevent.spawn(func, 'work3'),
             }
         )
     
     
     if __name__ == '__main__':
         start_time = time.time()
         main()
         print('一共执行{}时间'.format(time.time()-start_time))
     
     # 一共执行17.179136753082275时间	不使用协程
     # 一共执行6.5413126945495605时间	使用协程
    

5.6,并发图片下载器

利用在网络IO传输的过程中消耗的时间差来切换到另外一张图片的下载,节省时间

 # !/usr/bin/env python
 # _*_ coding:utf-8 _*_
 # author:满怀心 2019/8/6 12:23
 """
 # code is far away from bugs with the god animal protecting
     I love animals. They taste delicious.
               ┏┓      ┏┓
             ┏┛┻━━━┛┻┓
             ┃      ☃      ┃
             ┃  ┳┛  ┗┳  ┃
             ┃      ┻      ┃
             ┗━┓      ┏━┛
                 ┃      ┗━━━┓
                 ┃  神兽保佑    ┣┓
                 ┃ 永无BUG!   ┏┛
                 ┗┓┓┏━┳┓┏┛
                   ┃┫┫  ┃┫┫
                   ┗┻┛  ┗┻┛
 """
 import gevent
 from gevent import monkey
 import requests
 
 
 monkey.patch_all()
 
 
 def download_img(img_name, img_url):
     res = requests.get(img_url)
     with open(r'{}'.format(img_name), 'wb') as f:
         f.write(res.content)
 
 def main():
     gevent.joinall({
         gevent.spawn(download_img, '1.jpg','https://i0.hdslb.com/bfs/sycp/creative_img/201908/ad72e7afe1c2e6e60bf4d219d1b5acf4.jpg@880w_440h.jpg'),
         gevent.spawn(download_img, '2.jpg','https://i0.hdslb.com/bfs/archive/91d5d1c1922ed3f04a0c161a2a31aa5fba841819.jpg@160w_100h.jpg')
     })
 
 
 if __name__ == '__main__':
     main()