从yield到yield from

yield
带有 yield 的函数不再是一个普通函数,而是一个生成器generator
把生成器当作管道

def add_A(seq):
    for item in seq:
        yield item + '-A'


def add_B(seq):
    for item in seq:
        yield item + '-B'


def add_C(seq):
    for item in seq:
        yield item + '-C'


seq = ['apple', 'banana', 'orange']

stacked_generator = add_C(add_B(add_A(seq)))

for item in stacked_generator:
    print(item)

yield 可以接受传值

def receiver():
    while True:
        received_item = yield
        print('收到:', received_item)

def caller():
    recv = receiver()
    next(recv) # 使生成器前进到 yield
    for i in 'abcd':
        recv.send(i)

caller()

那 send 函数的返回值是什么呢?

def receiver():
    call_times = 0
    while True:
        item = yield call_times
        print('收到:', item)
        call_times += 1

def caller():
    recv = receiver()
    next(recv)
    for i in 'abcd':
        ret_value = recv.send(i)
        print('返回值: ', ret_value)

caller()

给生成器传值
可以通过调用生成器的 send 方法来向生成器传值,这将让生成器从上次暂停的 yield 前进到下个 yield,并将产出值作为 send 的返回值。当我们没有使用send方法时generator只能产出而不能消费,当我们使用了send后generator便可以进行消费了

def generator():
    item = yield 'a'#先将'a'返回给调用方,让后将send的值让generator进行消费
    print(item)
    another_item = yield 'b'

gen = generator()
print(next(gen))
print(gen.send(1))

关闭一个生成器
通过调用生成器 close 方法可以生成器在 yield 语句处抛出 GeneratorExit。这时仅允许 return,如果没有捕捉这个错误,生成器会静默关闭,不抛出错误。

def generator():
    times = 0
    while True:
        yield times
        times += 1

gen = generator()
print(next(gen))
print(next(gen))
gen.close() # 不会抛出错误

抛出错误

def generator():
    try:
        yield 'apple'
    except RuntimeError as e:
        print('捕捉到:', e)
    yield 'banana'

gen = generator()
print(next(gen))
print(gen.throw(RuntimeError, '运行错误'))

生成器返回值
如果在生成器函数中加上 return 那在运行到 return 时将会把返回值作为 StopIteration 的值传递出去。这个是 Python3 的特性,Python2 生成器不能返回某个值。

def generator():
    yield
    return 'apple'

g = generator()
next(g)
try:
    next(g)
except StopIteration as e:
    print(e)

yield from
Python3.3版本的PEP 380中添加了yield from语法,生成器委托
使用 yield from 可以帮你对一个生成器不断调用 next 函数,并返回生成器的返回值。言下之意是你可以在生成器里调用生成器。
我们可以用yeild from来简单的实现chain这个模块:
yield from iterable,from后面是一个iterable对象

def my_chain(*args, **kwargs):
    for my_iterable in args:
        yield from my_iterable
        # for value in my_iterable:
        #     yield value


for value in my_chain([12, 21, 11, 10, 23, 12], 'liyuanchao'):
    print(value)

三个概念理清楚

def gens():
    yield 'test'


def g1(gen):
    yield from gen


def test():
    g = g1(gens())
    g.send(None)

1. test为调用方,g1为委托生成器,gen为子生成器
2. yield from会在调用方和子生成器之间建立一个双向通道,这里的gen直接和test建立双向通道,也就是说子生成器gen yield出来的值是直接交给test的,并且这里的send的值是直接发送给子生成器的。

yield from的两个高级的用法的例子
例1.Bobby牌商品的统计

final_result = {}


def sales_sum(pro_name):
    total, nums = 0, []
    while True:
        x = yield
        print(pro_name + "销量:", x)
        if not x:
            break
        total += x
        nums.append(x)
    return total, nums


def middle(key):
    while True:
        final_result[key] = yield from sales_sum(key)
        print(key + "销量统计完成!!!")


def main():
    data_sets = {
        "bobby牌面膜": [1200, 1500, 3000],
        "bobby牌手机": [28, 55, 98, 108],
        "bobby牌大衣服": [280, 560, 778, 70]
    }
    for key, data_set in data_sets.items():
        print('start key:', key)
        m = middle(key)
        m.send(None)
        for value in data_set:
            m.send(value)
        m.send(None)
    print("final_result:", final_result)

例2.体重统计

from collections import namedtuple
Result = namedtuple('Result', 'count average')
# the subgenerator
def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield
        if term is None:
            break
        total += term
        count += 1
        average = total / count
    return Result(count, average)
# the delegating generator
def grouper(results, key):
    while True:
        #只有当生成器averager()结束,才会返回结果给results赋值
        results[key] = yield from averager()
def main(data):
    results = {}
    for key, values in data.items():
        group = grouper(results, key)
        next(group)
        for value in values:
            group.send(value)
        group.send(None)
    report(results)
#如果不使用yield from,仅仅通过yield实现相同的效果,如下:
def main2(data):
    for key, values in data.items():
        aver = averager()
        next(aver)
        for value in values:
            aver.send(value)
        try: #通过异常接受返回的数据
            aver.send(None)
        except Exception as e:
            result = e.value
            print(result)
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))
data = {
    'girls;kg':[40.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5],
    'girls;m':[1.6, 1.51, 1.4, 1.3, 1.41, 1.39, 1.33, 1.46, 1.45, 1.43],
    'boys;kg':[39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3],
    'boys;m':[1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46],
}
if __name__ == '__main__':
    main(data)

总结
gen = generator()

  • 使一个生成器前进 next(gen)

  • 传递一个值 gen.send(item)

  • 中止生成器 gen.close()

  • 抛出错误 gen.throw(exc, val, tb)

  • 委托 result = yield from gen

1.迭代器(即可指子生成器)产生的值直接返还给调用者
2.任何使用send()方法发给委派生产器(即外部生产器)的值被直接传递给迭代器。如果send值是None,则调用迭代器next()方法;如果不为None,则调用迭代器的send()方法。如果对迭代器的调用产生StopIteration异常,委派生产器恢复继续执行yield from后面的语句;若迭代器产生其他任何异常,则都传递给委派生产器。
3.除了GeneratorExit 异常外的其他抛给委派生产器的异常,将会被传递到迭代器的throw()方法。如果迭代器throw()调用产生了StopIteration异常,委派生产器恢复并继续执行,其他异常则传递给委派生产器。
4.如果GeneratorExit异常被抛给委派生产器,或者委派生产器的close()方法被调用,如果迭代器有close()的话也将被调用。如果close()调用产生异常,异常将传递给委派生产器。否则,委派生产器将抛出GeneratorExit 异常。
5.当迭代器结束并抛出异常时,yield from表达式的值是其StopIteration 异常中的第一个参数。
6.一个生成器中的return expr语句将会从生成器退出并抛出 StopIteration(expr)异常。
参考:
Python异步编程详解
生成器进化到协程
Python Async/Await入门指南

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页