生成器与 yield
函数使用 yield 关键字可以定义生成器对象. 生成器是一个函数. 它生成一个值的序列, 以便在迭代中使用, 例如:
- def countdown(n):
- print('倒计时:%s' % n)
- while n> 0:
- yield n
- n -= 1
- return
- c = countdown(10)
如果调用该函数, 就会发现其中的代码不会开始执行, 相反它会返回一个生成器对象, 接着该生成器对象就会在__next__() 被调用时执行函数.
- print(c.__next__())
- print(c.__next__())
- print(c.__next__())
调用__next__() 时, 生成器函数将开始执行语句, 知道遇到 yield 语句为止. yield 语句在函数执行停止的地方生成一个结果, 直到再次调用 next(). 然后继续执行 yield() 之后的语句. 通常不会在生成器上直接调用 next() 方法, 而是通过 for 语句, sum() 或一些消耗序列的其他操作使用生成器. 例如:
- for i in countdown(10):
- print(i)
- a = sum(countdown(10))
- print(a)
生成器函数完成的标志是返回或引发 StopIteration 异常, 这标志着迭代的结束. 如果生成器在完成时返回 None 以外的值都是不合法的. 生成器使用时存在一个棘手的问题, 即生成器函数仅被部分消耗, 例如:
- for n in countdown(10):
- if n == 2:
- break
- print(n)
在这个例子中, 通过调用 break 退出循环, 而相关的生成器也没有全部完成. 为了处理这种情况, 生成器对象提供方法 close() 标识关闭. 不再使用或删除生成器时, 就会调用 close() 方法. 通常不必手动调用 close() 方法, 但也可以这么做.
在生成器函数内部, 在 yield 语句上出现 GeneratorExit 异常时就会调用 close() 方法. 也可以选择捕捉这个异常, 以便执行清理操作
- def countdown2(n):
- print('倒计时:%s' % n)
- try:
- while n> 0:
- yield n
- n -= 1
- except GeneratorExit:
- print('GeneratorExit %s' % n)
- c = countdown2(2)
- print(next(c))
- print(next(c))
- del c
虽然可以捕捉 GenratorExit 异常, 但对于生成器函数而言, 使用 yield 语句处理异常并生成另一个输出值是不合法的. 另外, 如果程序当前正在对生成器进行迭代, 不应该通过另一个的执行线程或从信号处理程序异步调用该生成器上的 close() 方法.
协程与 yield 表达式
在函数内, yield 语句还可以作为表达式使用, 出现在赋值运算符的右边, 例如:
- def receive():
- print('Ready to receive')
- while True:
- n = yield
- print('Got %s' % n)
以这种方式使用 yield 语句的函数称为协程, 向函数发送值时函数将执行. 它的行为也十分类似于生成器
- r = receive()
- r.__next__()
- r.send(1)
在这个例子中, 一开始调用__next__() 是必不可少的, 这样协程才能执行第一个 yield 表达式之前的语句. 这时, 协程会挂起, 等待相关生成器对象 r 的 send() 方法给他发送一个值.
传递给 send() 的值由协程中的 yield 表达式返回. 接收到值后, 协程就会执行语句, 直到遇到下一条 yield 语句.
在协程中需要调用 next() 这件事很容易被忽略, 这经常称为错误出现的原因. 因此, 建议使用一个能够自动完成该步骤的装饰器来包装协程.
- def coroution(func):
- def start(*args, **kwargs):
- g = func(*args, **kwargs)
- g.__next__()
- return g
- return start
- # 使用这个装饰器就可以像下面这样编写和使用协程:
- @coroution
- def receive():
- print('Ready to receive')
- while True:
- n = yield
- print('Go %s' % n)
- r = receive()
- r.send('hello world') # 无需初始调用. next() 方法
协程一般会不断地执行下去, 除非被显式关闭或者自己退出. 关闭后如果继续给协程发送值就会引发 StopIteration 异常. 正如前面关于生成器的内容中讲到的那样, close() 操作将在协程内部引发 GeneratorExit 异常.
可以使用 throw(exctype [, value [.tb]]) 方法在协程内部引发异常, 其中 exctype 是指异常类型, value 是指异常的值, 而 tb 是指跟踪对象例如:
r.throw(RuntimeError, "You're hosed")
以这种方式引发的异常将在协程中当前执行的 yield 语句处出现. 协程可以选择捕捉异常并以正确方式处理它们. 使用 throw() 方法作为给协程的异步信号并不安全 -- 永远都不应该通过单独的执行线程或信号处理程序调用这个方法.
如果 yield 表达式中提供了值, 协程可以使用 yield 语句同时接收和发出返回值, 例如:
- def line_splitter(delimiter=None):
- print("Ready to split")
- result = None
- while True:
- line = yield result
- result = line.split(delimiter)
- l = line_splitter(',')
- l.__next__()
- print(l.send("a,b,c"))
首个__next__() 调用让协程向前执行到 yield result, 这将返回 result 的值 None. 在接下来的 send() 调用中, 接收到的值被放在 line 中并拆分到 result 中.
send() 方法的返回值就是传递给下一条 yield 语句的值. 换句话说, send() 方法的返回值来自下一个 yield 表达式, 而不是接收 send() 传递的值的 yield 表达式.
如果协程返回值, 需要小心处理使用 throw() 引发的异常. 如果使用 throw() 在协程中引发一个异常, 传递给协程中下一条 yield 语句的值将作为 throw()
方法的结果返回. 如果需要这个值却又忘记保存它, 它就会消失不见.
yield from
yield from 是在 Python3.3 才出现的语法, 后面需要加的是可迭代对象.
- a = 'qwertt'
- def str_to_list():
- yield from a
- def str_to_list2():
- for i in a:
- yield i
- print(list(str_to_list()))
- print(list(str_to_list2()))
yield from 的主要功能是打开双向通道, 把最外层的调用方与最内层的子生成器连接起来, 这样两者可以直接发送和产出值, 还可以传入异常
而不用在位于中间的协程中添加大量处理异常的样板代码.
双向通道: 调用方通过 send() 直接发送信息给子生成器, 而子生成器 yield 的值, 也直接返回给调用方
从 Python 3.5 开始引入了新的语法 async 和 await , 而 await 替代的就是 yield from
来源: https://www.cnblogs.com/niuu/p/10150402.html