Python协程前奏-深度剖析yield、yield from

1 yield

函数体中出现yield的关键字,这个函数调用就是一个生成器

def gen():
    print("generator start")
    x = yield 1        
    print("received x =",x)
    
print(type(gen()))  # <class 'generator'>

一个生成器对象有多种状态,可以通过客户端代码与生成器对象进行交互

客户端代码与生成器对象交互的三个主要方法:

  • send()
    生成器状态在GEN_SUSPENDED时,通过send方法向生成器传递值,生成器必须预激活,next(gen)或者gen.send(None)都可以用于预激活,如果不激活直接传递非None值,解释器会直接报错。
  • throw()
    向生成器抛出一个异常,如果生产期内部不处理这个异常,异常会上浮到客户端代码,如果客户端代码不捕获这个异常,那么会直接报错
  • close()
    给生成器一个停止停止。

下面的示例大家可以跑一下,如果能够理解这个过程,那么应该对生成器和yield有些理解

import inspect
from functools import wraps


def prime_generator(func):
    """
    预激活生成器的装饰器
    :return:
    """

    @wraps(func)
    def wrapper(*args, **kwargs):
        gen = func(*args, **kwargs)
        next(gen)
        return gen

    return wrapper


@prime_generator
def average():
    """
    生成器函数
    :return:
    """
    count = 0
    total = 0
    avg = 0
    while True:
        try:
            x = yield count, total, avg
        except ValueError:
            print("输入的值不符合要求")
            continue
        if x == None:
            break
        count += 1
        total += x
        avg = total / count


def client():
    """
    客户端代码
    :return:
    """
    avg = average()  # 创建生成器对象
    print(inspect.getgeneratorstate(avg))  # GEN_CREATED
    # s1 = next(avg) # 预激活生成器,生成器能运行,运行到第一个yield进入等待状态 , avg.send(None) 等同于 next(avg)
    # print(s1)
    while True:
        x = input("请输入值:")
        if x == "q":
            break
        try:
            x = float(x)
            res = avg.send(x)
            print(f"count:{res[0]},total:{res[1]},avg:{res[2]}")
        except ValueError:
            avg.throw(ValueError)
        print(inspect.getgeneratorstate(avg))  # GEN_SUSPENDED

    avg.close()
    print(inspect.getgeneratorstate(avg))  # GEN_CLOSED


if __name__ == '__main__':
    client()

在这里插入图片描述

2 yield from

引入yield from

def gen():
    for i in "AB":
        yield i
    for i in range(1,3):
        yield i

g = gen()

for i in g:
    print(i)
  
"""
A
B
1
2
"""

下面代码的输出结果与上面相同

def gen():
    yield from subgen()
    yield from subgen2()


def subgen():
    for i in "AB":
        yield i


def subgen2():
    for i in range(1, 3):
        yield i


g = gen()

for i in g:
    print(i)
"""
A
B
1
2
"""

首先,yield和yield from关键字必须写在函数体内。
含有yield from关键字的生成器称为委派生成器,yield from会把执行权交给其后面的生成器参数,当子生成器抛出StopIteration,执行权回到委派生成器内部。

from collections import namedtuple

Result = namedtuple('Result', ['count', 'average'])


def averager():
    total = 0.0
    count = 0
    average = 0
    while True:
        temp = yield
        if temp is None:
            break
        total += temp
        count += 1
        average = total / count
    return Result(count, average)


def grouper(results, key):
    while True:
        results[key] = yield from averager()


def report(results):
    for key, result in sorted(results.items()):
        group, unit = key.split(';')
        print('{:2} {:5} averaging {:.2f}{}'.format(result.count, group, result.average, unit))


def main(data):
    results = {}
    group = None
    for k, v in data.items():
        group = grouper(results, k)
        next(group)
        for v_ in v:
            group.send(v_)
        group.send(None)
    report(results)
    if group:  # 停止委派器生成器
         group.close()


data = {
    'girls;kg': [40, 41, 42, 43, 44, 54],
    'girls;m': [1.5, 1.6, 1.8, 1.5, 1.45, 1.6],
    'boys;kg': [50, 51, 62, 53, 54, 54],
    'boys;m': [1.6, 1.8, 1.8, 1.7, 1.55, 1.6],
}

if __name__ == '__main__':
    main(data)

通过一张图,来剖析一下这段代码的执行流程:

在这里插入图片描述
我的文字表达能力可能有限,解释的可能不清晰,如果读者有条件,可以把代码Copy到Pycharm中,打上断点,debug一下,看一下代码的执行流程,这样能够帮助大家更好的理解。

3 通过yield实现简单的生产者消费者

def consumer():
    while True:
        x = yield
        print(f"消费者消费了{x}")


def productor(c):
    next(c)
    while True:
        x = random.randint(0, 100)
        print(f"生产者生产了{x}")
        c.send(x)
        time.sleep(1)


if __name__ == '__main__':
    c = consumer()
    productor(c)

yield和yield 是python异步程序的基础,所以这是很重要的概念,这些概念相比同步还是比较抽象,所以不是很好理解,但是很重要。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

kobe_OKOK_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值