python协程讲解

多任务之协程

  • 一、迭代器
    • 1. 迭代器介绍:
    • 2. 可迭代对象
    • 3. 判断是否可以迭代:
    • 4. 迭代器的用途和优点:
    • 5. 自己创建一个迭代器类的过程
    • 6. 自己创建迭代器的实现
  • 二、生成器
    • 1. 生成器介绍
    • 2. 创建生成器方法
  • 三、协程
    • 1. 介绍
    • 2.yeid
    • 2. greenlet
    • 3. gevent
  • 四、案例
    • 1.斐波那契数列
      • 1.1 普通版
      • 1.1 迭代器版
      • 1.2 生成器版
    • 2. 并发的图片下载器
  • 五、总结
    • 1. yeid关键字
    • 2.协程与线程的区别

一、迭代器

1. 迭代器介绍:

  1. 迭代是访问元素集合的一种方式;
  2. 迭代器是一个可以记住遍历的位置的对象;
  3. 迭代器对象从集合的第一个元素开始访问,直到所有元素被访问完结束;
  4. 迭代器只能往前不会后退

2. 可迭代对象

  1. 可迭代对象有:list、tuple、str等类型数据使用for…in…的循环语法从其中一次拿到数据进行使用,我们吧这样的过程称为遍历,也叫做迭代

3. 判断是否可以迭代:

  1. 判断XXXX是否是可迭代对象:

    from collections import Iterable
    isinstance(XXXX, Iterable): 返回是True则可以迭代
    

4. 迭代器的用途和优点:

  1. 用途:可以代替range()的遍历 --> 元组转列表就是用的迭代器
  2. 优点: 完成一件事情,占用空间小,不存放返回结果,只存放生成的过程

5. 自己创建一个迭代器类的过程

  1. 首先要让其称为可迭代对象: 类中要有 __iter__方法
  2. 调用类的__iter__方法得到其返回值
  3. __iter__方法的返回值要是一个迭代器
  4. 迭代器对象要含有:__iter__方法和__next__方法

6. 自己创建迭代器的实现

  1. __iter__方法返回另外一个迭代器对象
    """自定义一个迭代器类"""
    import time
    from collections.abc import Iterable
    from  collections.abc import Iterator
    
    
    class Classmate(object):
        """自定义一个存名字的类"""
        def __init__(self):
            # 定义一个存放名字的列表
            self.names = list()
    
        def add(self, name):
            # 向列表中添加名字
            self.names.append(name)
    
        def __iter__(self):
            """如果想要一个对象成为一个迭代的对象,即可以使用for,那么必须实现__iter__方法, 而且他的返回值的类中必须有__next__方法"""
            return ClassIterator(self)
    
    
    class ClassIterator(object):
        def __init__(self, obj):
            self.obj = obj
            self.current = 0
    
        def __iter__(self):
            pass
    
        def __next__(self):
            if self.current < len(self.obj.names):
                # print(self.obj.names[self.current])
                ret = self.obj.names[self.current]
                self.current += 1
                return ret
            else:
                # 如果超出则返或异常,则for 将会自动停下来
                raise StopIteration
    
    
    def main():
        classmate = Classmate()
        classmate.add("张三")
        classmate.add("李四")
        classmate.add("王五")
        print("判断classmate是否是可以迭代的对象:", isinstance(classmate, Iterable))
        class_iterator = iter(classmate)
        print("判断classmate_iterator是否是迭代器:",isinstance(class_iterator, Iterator))
        # print(next(class_iterator))
        for name in classmate:
            time.sleep(1)
            print(name)
    
    
    if __name__ == "__main__":
        main()
    
  2. __iter__方法将自己的做为一个迭代器对象进行返回
    """自定义一个迭代器类"""
    import time
    from collections.abc import Iterable
    from  collections.abc import Iterator
    
    
    class Classmate(object):
        """自定义一个存名字的类"""
        def __init__(self):
            # 定义一个存放名字的列表
            self.names = list()
            self.current = 0
    
        def add(self, name):
            # 向列表中添加名字
            self.names.append(name)
    
        def __iter__(self):
            """如果想要一个对象成为一个迭代的对象,即可以使用for,那么必须实现__iter__方法, 而且他的返回值的类中必须有__next__方法"""
            return self
    
        def __next__(self):
            if self.current < len(self.names):
                # print(self.obj.names[self.current])
                ret = self.names[self.current]
                self.current += 1
                return ret
            else:
                # 如果超出则返或异常,则for 将会自动停下来
                raise StopIteration
    
    
    
    def main():
        classmate = Classmate()
        classmate.add("张三")
        classmate.add("李四")
        classmate.add("王五")
        print("判断classmate是否是可以迭代的对象:", isinstance(classmate, Iterable))
        class_iterator = iter(classmate)
        print("判断classmate_iterator是否是迭代器:",isinstance(class_iterator, Iterator))
        # print(next(class_iterator))
        for name in classmate:
            time.sleep(1)
            print(name)
    
    
    if __name__ == "__main__":
        main()
    

二、生成器

1. 生成器介绍

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

2. 创建生成器方法

  1. 方法一:
    # 将创建列表的时的[]换成()就可以了
    num = (i for i in rnage(10))
    # 返回值(num)是一个生成器
    
  2. 方法二:yeid
    使用函数–>要保证函数中有yeid – > 请往下接着看
  3. 方法三:generator

三、协程

1. 介绍

  1. 协程又称为微线程;
  2. 在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定

2.yeid

  1. 两个函数间的多任务
    """用yield完成多任务----协程"""
    import time
    
    
    def test1():
        while True:
            print("----1----")
            time.sleep(0.1)
            yield
    
    
    def test2():
        while True:
            print("----2----")
            time.sleep(0.1)
            yield
    
    
    def main():
        # 创建一个生成器对象
        t1 = test1()
        t2 = test2()
        # 先让t1运行一会,当t1遇到yield的时候,返回到main的while True中
        # 然后执行t2,当t2遇到yield的时候,在切换到t1中
        # 这样t1/t2/t1/t2/t1/t2的交替运行,最终实现了多任务  ---- 协程
        while True:
            next(t1)
            next(t2)
    
    
    
    if __name__ == "__main__":
        main()
    
  2. yeid中的send和next:
    next():获取数据 – > 一般第一次都用next()
    send():可以传递数据 – > 如果这个当第一个的时候要传递的参数为None ; 用途:(可以将生成时的值将改变为初始的状态)
    """生成器中的send和next"""
    
    def Create_num(num):
        a, b = 0,1
        create_nums = 0
        while create_nums < num:
            # ret 用来接收send发送过来的值
            ret = yield a
            a, b = b, a+b
            print(">>>ret>>>",ret)
            create_nums +=1
    
    create_num = Create_num(10)
    
    # ret  = create_num.send(None)  # 如果非要使用send的话传递的值要为None
    # print(ret)
    
    ret = next(create_num)
    print(ret)
    # 一般send不作为第一个
    ret  = create_num.send("adnsdj")
    print(ret)
    

2. greenlet

为了更好的使用协程来完成多任务,python中greenlet模块对yeid进行了封装,使得切换更简单

from greenlet import greenlet
import time



def test1():
    while True:
        print("----1----")
        # 调用test2
        gr2.switch()
        time.sleep(0.1)


def test2():
    while True:
        print("----2----")
        # 调用test1
        gr1.switch()
        time.sleep(0.1)


gr1 = greenlet(test1)
gr2 = greenlet(test2)

# 启动第一个函数
gr1.switch()

3. gevent

  1. gevent当程序遇到延时操作的使用自动切换任务

  2. 必须使用的是gevent中的sleep,如果程序已经有多中延时操作,可以在开头使用下面的语句将其替换

    # 想要用gevent,代码中的延时操作都要用gevent中的 ,如果不这样做的话可以用一下方法:打个补丁:
    # 加上以下内容:
    from gevent import monkey
    monkey.patch_all()
    # 加上后那些延时操作都会自动改为gevent版的
    
  3. 举例:
    将greenlt版的改为gevent版的

    """使用gevent完成多任务 - 协程"""
    import gevent
    import time
    from gevent import monkey
    
    
    monkey.patch_all()  # 将程序中的延时操作都改成gevent类型
    
    
    def f1(n):
        for i in range(n):
            print(gevent.getcurrent(), i)  # 打印那个线程执行
            time.sleep(1)
    
    
    def f2(n):
        for i in range(n):
            print(gevent.getcurrent(), i)  # 打印那个线程执行
            gevent.sleep(1)  # gevent 中的延时 如果没有monkey.patch_all() 必须要将延时操作都改成gevent类型
    
    
    def main():
        """写法1"""
        # print("----1----")
        # g1 = gevent.spawn(f1, 5)
        # print("----2----")
        # g2 = gevent.spawn(f2, 5)
        # print("----3----")
        # g1.join()  # 等待g1运行完
        # g2.join()  # 等待g2运行完
    
        # 写法2
        gevent.joinall([
            gevent.spawn(f1, 5),
            gevent.spawn(f2, 5)
        ])
    
    
    if __name__ == "__main__":
        main()
    

四、案例

1.斐波那契数列

1.1 普通版

"""普通的斐波那契数列"""
a = 0
b = 1
fibonacci = list()
for i in range(10):
    fibonacci.append(a)
    a, b = b, a + b

for num in fibonacci:
    print(num)

1.1 迭代器版

"""斐波那契数列 迭代器版"""


class Fibonacci(object):
    def __init__(self, number):
        self.number = number  # 总数
        self.current = 0  # 计数
        self.a = 0
        self.b = 1
        pass

    def __iter__(self):
        return self
    def  __next__(self):
        if self.current < self.number:
            ret = self.a
            self.a, self.b = self.b, self.a + self.b
            self.current +=1
            return ret
        else:
            # 超出停止
            raise StopIteration


fibonacci = Fibonacci(10)
for num in fibonacci:
    print(num)

1.2 生成器版

"""普通的斐波那契数列 生成器版"""

def Fibonacci(num_all):
    print("----1----")
    a, b = 0, 1
    current_num = 0
    while current_num < num_all:
        print("----2----")
        yield a  # 如果函数中有yeid语句,那么这个就不在是函数, 而是一个生成器的模板
        print("----3----")
        a, b = b, a+b
        current_num += 1
        print("----4----")



# 如果在调用 Fibonacci的时候,发现函数中有yield那么此时,不是调用函数,而是创建了一个生成器对象
fibonacci = Fibonacci(10)


x = next(fibonacci)
print(x)

x = next(fibonacci)
print(x)

# for num in fibonacci:
#     print(num)

2. 并发的图片下载器

"""并发下载器图片"""
import urllib.request
import gevent
from gevent import monkey
import time


monkey.patch_all()  # 替换延时操作


def download(image_file, img_url):
    img = urllib.request.urlopen(img_url)
    time.sleep(1)
    img_f = img.read()
    with open(image_file, "wb") as f:
        f.write(img_f)


def main():
    f1 = "../../static/image/down_img/1.jpg"
    f2 = "../../static/image/down_img/2.jpg"
    gevent.joinall([
        gevent.spawn(download, f1, "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1906469856,4113625838&fm=26&gp=0.jpg" ),
        gevent.spawn(download, f2, "https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3984473917,238095211&fm=26&gp=0.jpg")
    ])
    print("下载完成")



if __name__ == "__main__":
    main()

五、总结

1. yeid关键字

  1. 只要有yield关键字,那么虽然看上去是调用函数,实际上已经变成了创建一个 生成器对象
  2. 通过next调用 生成器,可以让 这个带有yield的def代码块,开始执行
    1.1 如果是第一次执行,则从def代码块的开始部分执行,直到遇到yield为止,并且把yield关键字后的数值返回,当做next()的返回值
    1.2 如果不是第一次执行,则从上一次暂停的位置执行(即从上一次yield关键字的下一个语句开始执行),直到遇到下一次yield为止,并且把yield关键字后的数值返回,当做next()的返回值
  3. 要知道一个def代码块,只要有yield就不再是函数,而是生成器
  4. 要知道调用def代码块(普通函数)与调用带有yield的def代码块(生成器)的调用方式不同
  5. 要注意return与yield的功能也不同
    5.1 return接收一个函数,且有返回值
    5.2 yield暂停执行一个函数,且有返回值

2.协程与线程的区别

  1. 在实现多任务时, 线程切换从系统层面远不止保存和恢复 CPU上下文这么简单。
  2. 操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据,操作系统还会帮你做这些数据的恢复操作。 所以线程的切换非常耗性能。
  3. 但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住

你可能感兴趣的:(python,#,多任务,python,生成器)