生成器、协程

生成器、协程

生成器

9.1.1 生成器的本质 ★★★★

在 Python 中,使用了 yield 的函数被称为生成器(generator)。

跟普通函数不同,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器本质上就是一个迭代器。

在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。

示例如下:

def fei(n):
    num1, num2 = 0, 1
    current = 0
    while current < n:
        num = num1
        num1, num2 = num2, num1 + num2
        current += 1
        yield num

注意事项:

调用一个生成器函数,返回的是一个迭代器对象。

生成器的构造方法

方法一:

# 列表推导式
ret_01 = [i for i in range(1,11)]
print(ret_01)

控制台显示为:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# 生成器
ret_02 = (i for i in range(1,11))
print(ret_02)

控制台显示为:

<generator object <genexpr> at 0x0000013DE77B39E8>   # generator  生成器对象

总结:将列表推导式的 [ ] 改成 ( ) 后,就是一个generator生成器对象


方法二:

在使用生成器实现的方式中,我们将原本在迭代器__next__方法中实现的基本逻辑放到一个函数中来实现,但是将每次迭代返回数值的return换成了yield,此时新定义的函数便不再是函数,而是一个生成器了。简单来说:只要在def中有yield关键字的 就称为 生成器

示例如下:

def fei(n):
    num1, num2 = 0, 1
    current = 0
    while current < n:
        num = num1
        num1, num2 = num2, num1 + num2
        current += 1
        yield num
if __name__ == '__main__':
    ret = fei(10)
    # 查看生成器的第一种方式next()
    # 查看生成器的第二种方式强转,例如:list()、tuple()
    # 查看生成器的第三种方式for循环
    
StopIteration停止迭代异常
def fei(n):
    num1, num2 = 0, 1
    current = 0
    while current < n:
        num = num1
        num1, num2 = num2, num1 + num2
        current += 1
        yield num
if __name__ == '__main__':
    ret = fei(10)
    # 查看生成器的第一种方式next()
    while True:
        try:
            print(next(ret))
        except StopIteration:
            break
    # 查看生成器的第二种方式强转,例如:list()、tuple()
    # 查看生成器的第三种方式for循环
yield关键字讲解

yield关键字作用:

使用了yield关键字的函数不再是函数,而是生成器。(即:使用了yield的函数就是生成器)
yield关键字有两点作用:
保存当前运行状态(断点),然后暂停执行,即将生成器(函数)挂起
将yield关键字后面表达式的值作为返回值返回,此时可以理解为起到了return返回值的作用

可以使用next()函数让生成器从断点处继续执行,即唤醒生成器(函数)
Python3中的生成器可以使用return返回最终运行的返回值,而Python2中的生成器不允许使用return返回一个返回值(即可以使用return从生成器中退出,但return后不能有任何表达式)

协程的介绍

协程的概念描述

协程: 微线程,也称为用户级线程,在不开辟线程的基础上完成多个任务按照一定顺序交替执行
如何理解协程: 在def里面只看到一个yield关键字就是协程

协程的优势

优势01:协程最大的优势就是极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。(内部封装了cpu上下文)

优势02:不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

相比于进程和线程,协成的特点:协成可以在单个线程的基础上多个任务交替执行,所以协程切换任务资源很小,效率高

基本协程的构造

在def里面只看到一个yield关键字就是协程(实际上也可以当成生成器来看)

import time

# 定义协程1
def work_1():
    while True:
        print("work1...")
        time.sleep(0.1)
        yield

# 定义协程2
def work_2():
    while True:
        print("work2...")
        time.sleep(0.1)
        yield

# 创建协程
g1 = work_1()
g2 = work_2()
while True:
    next(g1)
    next(g2)

通过yield/send方式实现生产者/消费者模型:

def consumer():   # consumer 消费者
    r = ''
    while True:
        n = yield r
        print('[CONSUMER] Consuming %s...' % n)
 
def produce(c):   # produce 生产者
    c.send(None)
    for n in range(1, 6):
        print('[PRODUCER] Producing %s...' % n)
        r = c.send(n)
    c.close()
 
c = consumer()
produce(c)

greenlet模块

greenlet模块讲解

为了更好使用协程来完成多任务,python中的greenlet模块对其封装,从而使得切换任务变的更加简单
安装方式:
使用如下命令安装greenlet模块:

pip3 install greenlet
使用greenlet模块演示协程

案例演示:

import greenlet
import time
# greenlet封装的是yield,为了让程序员更好的使用协程
# 任务1
def work1():
    for i in range(5):
        print("work1....")
        time.sleep(0.3)
        # 切换到协程2执行对应的任务
        g2.switch()

# 任务2
def work2():
    for i in range(5):
        print("work2....")
        time.sleep(0.3)
        # 切换到协程1执行对应的任务
        g1.switch()

# 创建协程指定对应的任务
g1 = greenlet.greenlet(work1)
g2 = greenlet.greenlet(work2)
# 启动并切换到指定协程执行对应的任务
g1.switch()

使用greenlet实现生产者/消费者模型:

from greenlet import greenlet
 
def consumer(): # 消费者 - consumer
    while True:
        n = gr_produce.switch()
        print('[CONSUMER] Consuming %s...' % n)
 
def produce(): # 生产者 - produce
    for n in range(1, 6):
        print('[PRODUCER] Producing %s...' % n)
        gr_consumer.switch(n)
 
gr_consumer = greenlet(consumer)
gr_produce = greenlet(produce)
 
#切换到consumer中运行
gr_consumer.switch()

gevent模块

gevent模块简介

greenlet已经实现了协程,但是这个还的人工切换,是不是觉得太麻烦了,不要捉急,python还有一个比greenlet更强大的并且能够自动切换任务的模块gevent,其原理是当一个greenlet遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。
由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO

安装方式
使用如下命令安装gevent模块:

pip3 install gevent
使用gevent模块演示协程

gevent基本使用方法介绍:

  1. gevent.spawn(func, *args, …)方法用来生成协程,他接受一个函数作为参数
  2. gevent.joinall([t1, t2, …])方法用来启动协程轮询并等待运行结果
  3. gevent.sleep()用来模拟一个IO操作,阻塞后自动切换到另一个协程执行

gevent模块案例演示:

from gevent import monkey
# 打补丁,让gevent能够识别系统的耗时操作和网络请求延时操作
# 提示: 这行代码一定要先执行。
monkey.patch_all()
#总结: gevent封装的是greentlet, gevent可以完成协程之间自动切换
#任务1
def work1():
    # 获取当前任务执行的协程对象
    print("work1:", gevent.getcurrent())
    for i in range(10):
        print("work1")
        time.sleep(0.1)
        # gevent.sleep(0.1)

# 任务2
def work2():
    # 获取当前任务执行的协程对象
    print("work2:", gevent.getcurrent())
    for i in range(10):
        print("work2")
        time.sleep(0.1)
        # gevent.sleep(0.1)


# 创建协程指定对应的任务
g1 = gevent.spawn(work1)
g2 = gevent.spawn(work2)
# 让主线程等待协程执行完成以后程序再退出
g1.join()
g2.join()
# 方式2
gevent.joinall([g1, g2])
gevent综合案例

从网页中进行图片抓取:

import gevent
import urllib.request # 网络请求模块
from gevent import monkey

# 打补丁, 让gevent识别耗时操作
monkey.patch_all()

# 下载图片
def download_img(img_url, img_name):
    try:
        # 请求打开网络资源
        response = urllib.request.urlopen(img_url)
        with open(img_name, "wb") as img_file:
            while True:
                # 读取请求的资源数据
                img_data = response.read(1024)
                if img_data:
                    # 写入到指定图片名里面
                    img_file.write(img_data)
                else:
                    break
    except Exception as e:
        print("下载图片出现异常:", e)
    else:
        print("%s图片下载成功" % img_name)


if __name__ == '__main__':
    # 准备图片地址
    img_url1 = "https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3941403894,1764478959&fm=26&gp=0.jpg"
    img_url2 = "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1481212717,1253269835&fm=27&gp=0.jpg"
    img_url3 = "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=4190184537,2697009061&fm=27&gp=0.jpg"

    # 主线程等待所有的协程执行完成以后程序再退出
    gevent.joinall([
        # 创建协程并指派任务
        gevent.spawn(download_img, img_url1, "1.jpg"),
        # 创建协程并指派任务
        gevent.spawn(download_img, img_url2, "2.jpg"),
        # 创建协程并指派任务
        gevent.spawn(download_img, img_url3, "3.jpg")
    ])
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

(initial)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值