zoukankan      html  css  js  c++  java
  • python多任务---协程

    一、迭代器

       1.可迭代对象 

    我们把可以通过for...in...这类语句迭代 读取的一条数据供我们使用的对象称之为可迭代对象

       a>通过isinstance()可以判断一个对象是否可以迭代

            判断列表是否为可迭代对象

    import collections

       print(isinstance([],collections.Iterable)

            若输出值为True则说明是可迭代对象,若输出的值为False则说明是不可迭代对象 

       c>自定义一个能容纳数据的类,测试可迭代性

    from collections import Iterable
    class ClassMates(object):
        def __init__(self):
            self.names = []
        def add_name(self,name):
            self.names.append(name)

    my_classmates = ClassMates()
    my_classmates.add_name("lisi")
    my_classmates.add_name("zhangsan")
    my_classmates.add_name("wanger")
    #判断该类是否为可迭代类对象
    print(isinstance(ClassMates,Iterable))

       通过测试我们发现随便分装的一个存放多条数据的类也是不可迭代的

        2.可迭代对象的实质

        a>可迭代对象的本质

    可迭代对象的本质是提供一个中间人帮助我们 对存储的数据进行遍历使用,这个中间人我们称之为迭代器

        b>可迭代对象是如何取得迭代器的

    可迭代对象(Iterable)

    通过__iter__()取得迭代器(Iterator)

        c>如何定义一个可迭代类

    只要实现__iter__方法,返回一个迭代器(iterator),就可以实现可迭代类

     


    from collections import Iterable

    class ClassMate(object):

        def __init__(self):

            self.name=[]

            self.current = 0

    def add_names(self,name):

            self.name.append(name)

    def __iter__(self): ###通过该方法取得迭代器对象

            psaa

    my_classmate = ClassMate()

    my_classmate.add_names("lisi")

    my_classmate.add_names("wanger")

    my_classmate.add_names("zhangsan")

     

               print("是否为可迭代对象:",isinstance(my_classmate,Iterable))

    可以发现返回值是True

        3.再认识iter()函数与next()函数

        a>iter(Iterable) : 
    iter(iterable) -> iterator 通过可迭代对象(iterable) 取得该可迭代对象的迭代器(iterator),内部可迭代对象通过__iter__方法取得迭代器(iterator)
        b>next(iterator):

    Return the next item from the iterator

    返回迭代器指向的下一个位置的数据
      内部是通过迭代器的__next__()方法来取得下一个位置的数据

    注意: 当迭代器已经指向最后一个位置,当再调用next(),则抛 StopIteration 

        4.自定义迭代器

        a>迭代器的定义

    1.迭代器就是一个可迭代对象

    2.迭代器是实现了__next__(),__iter__()的类

        b>迭代器的特点

    1. 记录每次访问的位置,以及返回下一个位置的数据(next方法实现)

    2.默认从可迭代对象的第一个元素开始访问,知道所有的元素访问结束

    3. 只能线下迭代,不能向上迭代

    from collections import Iterable

    class ClassMate(object):

        def __init__(self):

            self.name=[]

            self.current = 0

        def add_names(self,name):

            self.name.append(name)

        def __iter__(self): ###通过该方法取得迭代器对象

            return self

        def __next__(self):

            if self.current< len(self.name):

                name = self.name[self.current]

                self.current+=1

                return name

            else:

                raise StopIteration

    my_classmate = ClassMate()

    my_classmate.add_names("lisi")

    my_classmate.add_names("wanger")

    my_classmate.add_names("zhangsan")

     

    print("是否为可迭代对象:",isinstance(my_classmate,Iterable))

     

         5.for...in...的本质

      for item in Iterable:

    循环的本质就是先通过iter()函数获取可迭代对象Iterable的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration的异常后循环结束

         6.应用场景

    1.迭代可迭代对象的数据

    2.用来自动生成数据 (在__next__()的方法中实现)

     

    一、生成器

        1.生成器介绍

    生成器(generator)。生成器是一种特殊的迭代器,它比迭代器更优雅

                    

        2.创建生成器方法1

        简单的生成器,把列表推导式的[]改为()即可

             G=(x*x for x in range(3))

                   

        3.创建生成器方法2(yield)

        a> yield引入

    1. 假如函数中有yield,则不再是函数,而是生成器

    2. yield 会产生一个断点,暂停函数,保存状态

    3. 假如yield后面紧接着一个数据,就会把数作为next()函数或者for ...in...迭代出的下一个值

    4. 可以通过next()唤醒生成器,让生成器从断点处继续执行

      编写斐波那契数列生成器

        

    class FibIterator(object):

        """斐波那切数列迭代器"""

        def __init__(self,n):

            self.n = n

            self.corrent = 0

            self.num1= 0

            self.num2= 1

        def __iter__(self):

            return self

        def __next__(self):

            if self.corrent<self.n:

                num= self.num1

                self.num1,self.num2 = self.num1+self.num2,num

                self.corrent+=1

                return num

            else:

                raise StopIteration

    if __name__ == '__main__':

        fib =   FibIterator(10)

        for num in fib:

            print(num,end=" ")

     

        b> yield实现生成器的执行过程

    # def fib(n):

    #     num_current = 0

    #     num1,num2 = 0,1

    #     while num_current<n:

    #         #print 打印斐波那切数列

    #         #假如函数中有yield,则不再是函数,而是生成器

    #         #yield会产生一个断点

    #         #假如yield 后面紧接着一个数据,就会把数据返回,

    #         #作为next()函数或者for后面迭代出的下一个值

    #

    #         m = yield num1

    #         print(m)

    #         num1,num2 = num2,num1+num2

    #         num_current+=1

    # if __name__ == '__main__':

    #     gen = fib(100)

    # #     for num in gen:

    # #         print(num)

    # ###函数中只要有yield就是一个生成器######

    # #使用next唤醒

    #     print(next(gen))

    #     print(next(gen))

    #     print(next(gen))

    # #使用send唤醒,send的好处是可以添加一个参数

    #     print(gen.send("你好"))

    #     print(gen.send("你好吗"))

        4.使用send唤醒生成器(generator)

    sendnext唤醒生成器不同:

    1. send next都可以唤醒生成器,但send(value)可以传值给生成器的断点处

    2. 使用:

    next(generator)

    generator.send("你好")

    3. generator.send(None)等价于next(generator)

            4. 注意: 第一次唤醒生成器时,假如使用send,则只能传None,因为刚开始执行生成器时,是没有断点的

    三、协程(Coroutine)

            1. 认识协程

              a>协程,又称微线程,纤程。英文名Coroutine

              b>协程是python个中另外一种实现多任务的方式,比线程更小占用更小执行单元

              c>最通俗的理解:就是一个可以暂停的函数,可以挂起的函数 time.sleep()

              d>通过yield生成器可以实现协程


            2. 协程和线程差异

                     线程包含在进程中,协程包含在线程中,一个线程可以包含多个协程

                     协程的切换开销比线程更小,协程在切换时,不需要保存和恢复线程的状态,并发量更高

                    

            3. 使用生成器实现协程

    import time


    def task1():

        while True:

            print("--task1--")

            yield

            time.sleep(1)

    def task2():

        while True:

            print("--task2--")

            yield

            time.sleep(1)  # 休眠, recvfrom() ,阻塞

    if __name__ == '__main__':

        # 创建生成器

        t1 = task1()

        t2 = task2()

        # 唤醒生成器

        while True:

            next(t1)

            next(t2)

                   

    四、协程-greenlet

            1. greenlet介绍

                     为了更好使用协程来完成多任务,python中的greenlet模块对yield生成器进行封装,对实现协程进行封装,从而使得实现协程更加简洁, greenlet就是一个封装协程的模块

             

            2. 安装方式及使用

               - 安装greenlet模块

                 sudo pip3 install greenlet

                   - pip3 用来安装python3模块的工具

                   - pip :用来安装python2模块的工具

               - 使用

                  import greenlet

                  g1=greenlet.greenlet(fun)

                  g1.switch() : 执行greenlet,切换到g1 greenlet任务中

                

            小案例: 有两个任务,一个不断打印A,一个不断打印B,通过greenlet实现多任务协程

             参考代码:12-使用greenlet实现多任务协程.py

    五、协程-gevent

            1. 使用greenlet的不足

                     任务多,人工切换很麻烦,

                     只有耗时的操作才切换,耗时的操作又有很多中,人工切换也很麻烦

            2. gevent使用原理:

                     1. gevent是对greenlet再一次封装

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

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

            3. gevent的使用

               - 安装模块

                 sudo pip3 install gevent

               - 使用模块

                 import gevent

                 g1 = gevent.spawn(task, 1)  # task:要执行的任务, 1: 表示传递给执行的函数的参数

                 # 执行gevent

        g1.join()

            gevent.sleep(1) # 使用gevent的sleep()

               问题: 使用gevent实现多任务

                参考代码:13-使用gevent实现协程.py

            4. gevent写法的优化

           

              a> 简化写法

                # 一行语句执行gevent实现多任务

                gevent.joinall([gevent.spawn(task, 1),

                                gevent.spawn(task, 2),

                                gevent.spawn(task, 3)])

              b> gevent打补丁

                             from gevent import monkey

                          monkey.patch_all()  # 把补丁

                   14-gevent优化.py

    六、进程、线程、协程区别

            进程是操作系统资源分配的单位

            线程是CPU调度的单位

            进程切换需要的资源最大,效率很低

            线程切换需要的资源一般,效率一般(当然在不考虑GIL的情况下)

            协程切换任务资源很小,效率高

            多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中 所以是并发

  • 相关阅读:
    redis参数AOF参数的bug
    tidb损坏tikv节点怎么恢复集群
    mysql主从延时临时解决办法
    python脚本批量杀死redis链接
    pt-online-schema-change 脚本化
    mysql查看锁等信息SQL
    mongo复制集脑裂问题如何处理
    日志收集及网络包收集方案
    各浏览器下载文件名不乱码的解决办法
    java 中 byte[]、File、InputStream 互相转换
  • 原文地址:https://www.cnblogs.com/zhangtq/p/9078076.html
Copyright © 2011-2022 走看看