Python 生成器与协程

在了解生成器之前,我们需要补充一下迭代器的知识:Python 迭代器(iterator)(若已了解,请自动忽略)

一、生成器

1.什么是生成器generator:

在迭代器中,我们需要来定义一个变量来记录当前所在的状态,进而才能根据当前状态生成下一个数据。而在生成器中,我们不需再定义一个变量来记住当前状态,会自动记住。可以说生成器是一类特殊的迭代器。

2.创建生成器

(1)小括号法

我们直接将列表推导式中的中括号改为小括号即可。因为生成器是一种特殊的迭代器,是所以可以用迭代的方式来验证。如下:

In [1]: a = [x*2 for x in range(10)]

In [2]: a
Out[2]: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

In [3]: a = a = (x*2 for x in range(10))

In [4]: a
Out[4]: <generator object <genexpr> at 0x7fc2d3646888>

In [5]: next(a)
Out[5]: 0

In [6]: next(a)
Out[6]: 2

In [7]: next(a)
Out[7]: 4

可以看出,由列表推导式生成 的列表是一次性生成所有数据,而后者是返回一个生成器,在调用时自动生成相应数据

(2)yield 法

如果一个函数中含有yield语句,那么这个函数就称为生成器。而在引用这个函数的时候不再是调用函数,而是创建一个生成器对象。在生成器运行的过程中,每次遇到yield都会暂停并保存当前所有的运行信息,返回yield后的值。在下一次运行时从当前位置开始运行。

3.yield的工作原理

我们通过一个例子来验证一下

import time


def test1():
    print("mama~")
    time.sleep(0.2)
    yield

def test2():
    print("我想吃烤山药~")
    time.sleep(1)
    yield

def main():
   while True:
        next(test1())
        print("============")
        next(test2())


if __name__ == "__main__":
    main()

运行结果如下:

根据结果可以看出,生成器每次都是在yield的地方返回数值停止到这个地方,直到下一次的调用,再从此处开始。

二、协程

1.协程的概念

协程,英文名是coroutine。其原理是当一个函数A正在执行时,可以随时中断,去执行函数B,然后再回来继续执行函数A。

2.greenlet

为了更好的使用协程来完成多任务,python中的greenlet对yield进行了封装,从而使得切换任务变得更加简单。在使用greenlet前,我们需要下载greenlet模块,sudo pip3 install greenlet 

import time
from greenlet import greenlet

def task1():
    while True:
        print("=====mama====")
        g2.switch()
        time.sleep(0.5)

def task2():
    while True:
        print("====我想吃烤山yao====")
        g1.switch()
        time.sleep(1)

g1 = greenlet(task1)
print("=================")
g2 = greenlet(task2)

g1.switch()

运行结果如下:

3.greenlet的工作原理

4.gevent

greenlet已经实现了协程,但是还是需要人工切换。而gevent实现了自动切换任务的功能。可以说是比greenlet更强的一个包装。其原理是当一个greenlet遇到i/o操作时,就会自动切换到其它的greenlet,等i/o操作结束后,再在适当的时候切换回来,继续执行未完成的程序。  由于io操作非常耗时,经常使程序处于等待状态,因此使用gevent就可以为我们自动切换协程,就能保证走总有greenlet在执行,而不是等待i/o。

import gevent
from gevent import monkey
import time
# 给所有的延时从操作打补丁:将其换为gevent内对应的延时操作
monkey.patch_all()

def f1(n):
    for i in range(n):
        print("___",i,"___")
        time.sleep(0.5)

def f2(n):
    for i in range(n):
        print("___",i,"___")
        time.sleep(0.5)

def f3(n):
    for i in range(n):
        print("___",i,"___")
        time.sleep(0.5)

g1 = gevent.spawn(f1,5) 
g2 = gevent.spawn(f1,5) 
g3 = gevent.spawn(f1,5) 

# 运行所有的协程
gevent.joinall([g1,g2,g3])

#g1.join()
#g2.join()
#g3.join()

运行效果如下:

5.案例

协程版图片下载器

import urllib.request
import gevent
from gevent import monkey

nums = 1
monkey.patch_all() # 将所有的等待程序改为gevent的等待程序

# 定义一个下载器
def downloader(url):
    global nums
    # 打开网址
    req = urllib.request.urlopen(url)
    # 读取内容
    data = req.read()
    # 写入本地文件
    with open("images/"+str(nums)+".jpg","wb") as f:
        f.write(data)
    nums += 1

def main():
    g1 = gevent.spawn(downloader,"https://sta-op.douyucdn.cn/dycatr/7e189c7fedb3aa96e84d9fefcc892ec1.png?x-oss-process=image/format,webp/quality,q_70")
    g2 = gevent.spawn(downloader,"https://sta-op.douyucdn.cn/dycatr/08dcd21b98a6b71bb1d37b0c30376734.png?x-oss-process=image/format,webp/quality,q_70")
    g3 = gevent.spawn(downloader,"https://sta-op.douyucdn.cn/dycatr/e3156f581df48b385f67fd1cc775f022.png?x-oss-process=image/format,webp/quality,q_70")
    g4 = gevent.spawn(downloader,"https://sta-op.douyucdn.cn/dycatr/a783ec694e455942f925801fb449243d.png?x-oss-process=image/format,webp/quality,q_70")
    g5 = gevent.spawn(downloader,"https://dss0.baidu.com/6LZXsjikBxIFlNKl8IuM_a/tb/cms/ngmis/file_1584672553807.jpg")
    g6 = gevent.spawn(downloader,"https://sta-op.douyucdn.cn/dycatr/3de8cf9f376ef5ee1bbb930868f7f402.png?x-oss-process=image/format,webp/quality,q_70")
    gevent.joinall([g1,g2,g3,g4,g5,g6])

if __name__ == "__main__":
    main()

 

 

猜你喜欢

转载自blog.csdn.net/qq_45807032/article/details/105287649