【python】生成器

2022-10-26 14:41:36 浏览数 (1)

天生骄傲

生成器

直接总结

  1. 创建生成器的方法
    1. 生成器表达式:(i for i in [1, 2])
    2. yield: 函数中出现yield这个函数就是生成器,函数(生成器)执行到yield时会返回yield后面的值,并暂停,知道下次被唤醒后会从暂停处接着执行
  2. 迭代生成器: 生成器实现了python迭代协议,可以使用next()for迭代
  3. 向生成器发送消息:如果yield出现在表达式右边,下一次生成器被send()唤醒时,上一个yield会接收send()发过来的消息赋值给上一个yield左边的变量。
  4. 唤醒生成器:
    1. next():唤醒生成器,让他运行到下一个yield处,返回yield出来的值
    2. send():唤醒生成器,并给生成器发送一个消息,同时让他运行到下一个yield处,返回yield出来的值,不能直接一开始就发送具体的数据
  5. 停止生成器:close()
  6. 发送异常:throw(type, val)
  7. yield from

创建与使用生成器

直接使用生成器表达式就可以快速创建一个生成器

代码语言:javascript复制
gen = (i for i in range(10))
print(type(gen))
for i in gen:
    print(i, end=" ")
    
# <class 'generator'>
# 0 1 2 3 4 5 6 7 8 9 

生成器实现了__next__可以使用next()来获取下一个值,当然也可以使用for循环遍历

更加常见的创建生成器的方式是使用yield关键字,一个函数如果出现yield关键字这个函数就会变成生成器,当函数运行到yield时会暂停下来,”返回“一个结果,下一次唤醒生成器时,函数会从停下来的地方继续运行

代码语言:javascript复制
def builder_demo():
    yield 0
    yield 1
    return 3


if __name__ == '__main__':
    bd = builder_demo()
    print(type(bd))
    print(next(bd))
    print(next(bd))
    print(next(bd))
    print(next(bd))
代码语言:javascript复制
<class 'generator'>
0
1
Traceback (most recent call last):
  File "E:/python/coroutine_test.py", line 12, in <module>
    print(next(bd))
StopIteration: 3

当没有下一个元素时调用next会抛出StopIteration异常,return的值会作为异常的值

代码语言:javascript复制
if __name__ == '__main__':
    bd = builder_demo()
    while True:
        try:
            print(next(bd))
        except StopIteration as e:
            print(f'result is {e.value}')
            break
代码语言:javascript复制
0
1
result is 3

yield不但可以“传递出值”,也可以接收值

代码语言:javascript复制
def builder_demo():
    news = yield 0
    print(f'news:  {news}')
    news1 = yield 1
    print(f'new1: {news1}')
    return 3


if __name__ == '__main__':
    bd = builder_demo()
    print(next(bd))
    result1 = bd.send("hello")
    print(result1)
    result2 = bd.send("hello2")
    print(result2)
代码语言:javascript复制
0
news:  hello
1
new1: hello2
Traceback (most recent call last):
  File "E:python/coroutine_test.py", line 14, in <module>
    result2 = bd.send("hello2")
StopIteration: 3

往暂停处传递消息使用生成器的send()方法,这个方法还可以自动迭代到生成器中的下一个对象(有next())的作用。

生成器是先yield出数据,等到下一次生成器被唤醒时,才会接收send()的数据,然后再yield出下一个数据,所以不能一开始就直接调用send()发送具体的值,会抛出TypeError

代码语言:javascript复制
TypeError: can't send non-None value to a just-started generator

应该先执行一次next()或执行一次generator.send(None),让生成器yield出数据,send(None)的作用与next()基本一样

生成器也可以停止,使用close()方法

代码语言:javascript复制
def builder_demo():
    news = yield 0
    print(f'news:  {news}')
    news1 = yield 1
    print(f'new1: {news1}')
    yield 4
    yield 5
    return 3


if __name__ == '__main__':
    bd = builder_demo()
    # print(next(bd))
    print(bd.send(None))
    result1 = bd.send("hello")
    print(result1)
    result2 = bd.send("hello2")
    print(result2)
    bd.close()
    print(next(bd))
代码语言:javascript复制
0
news:  hello
1
new1: hello2
4
Traceback (most recent call last):
  File "E:python/coroutine_test.py", line 20, in <module>
    print(next(bd))
StopIteration

close()之后再使用next(),会抛出StopIteration异常

除此之外,还可以向生成器发送异常

代码语言:javascript复制
if __name__ == '__main__':
    bd = builder_demo()
    print(bd.send(None))
    bd.throw(Exception, TypeError("throw new error"))
    print(next(bd))
代码语言:javascript复制
0
Traceback (most recent call last):
  File "E:python/coroutine_test.py", line 19, in <module>
    bd.throw(Exception, TypeError("throw new error"))
  File "E:python/coroutine_test.py", line 2, in builder_demo
    news = yield 0
TypeError: throw new error

yield from

yield from 是python3.3 PEP380 新添加的特性,它允许将一个生成器的部分操作委派给另一个生成器,除了向子生成器委派任务,yield from也可以直接作用于迭代器,将迭代器中的每个对象逐一yield出来,如:

代码语言:javascript复制
def demo(*args, **kwargs):
    for i in args:
        for j in i:
            yield j
# 等价于
def demo(*args, **kwargs):
    for i in args:
        yield from i

上面的函数其实就是itertools.chain() 作用是将多个迭代器中的元素迭代出来

生成器嵌套

1、调用方:调用委派生成器的客户端(调用方)代码 2、委托生成器:包含yield from表达式的生成器函数 3、子生成器:yield from后面加的生成器函数

yield from 可以架设一个调用方子生成器之间的双向桥梁

代码语言:javascript复制
final_result = {}


def calculate():
    total = 0
    nums = []
    while True:
        info = yield
        if not info:
            break
        total  = info
        nums.append(info)
    # return 的值会被赋值给yield from 左边的变量
    return total, nums


def middle(key: str, gen):
    while True:
        final_result[key] = yield from gen()
        print(final_result)


def main():
    data = {
        "apple": [230, 569, 234, 776],
        "banana": [564, 213, 798, 327],
        "strawberry": [98, 76, 120, 436, 55],
        "orange": [78, 67, 345, 124]
    }
    for key, value in data.items():
        # 不要传递calculate()!
        mid = middle(key, calculate)
        mid.send(None)  # 初激
        for v in value:
            mid.send(v)
        mid.send(None)


if __name__ == '__main__':
    main()

为什么“多此一举”架设一个“桥梁”?

  • yield from 在内部处理了大量可能的异常,简化开发,提高代码安全性和可读性

生成器的作用

  1. 适用于对大量数据的处理:如果要对产生的大量数据进一步处理时,使用容器就只能先得到所有要处理的数据,存到容器,再逐个对容器中的数据遍历,再加工,最后得到目标数据,这样第一步产生的“中间数据”只用一次,但仍需大量空间储存;使用生成器可以产生一个,加工一个,节约内存,提高效率
  2. 用于协程

0 人点赞