generator and yield
生成器表达式
生成器表达式的标准方式是以圆括号的形式,括号内可以是一个列表推导式。
generator_expression ::= "(" expression comp_for ")"
生成器表达式生语法和列表推导式相同,列表推导式是以大括号的形式存在。列表推导式是直接创建一个列表,但是由于受到内存的限制,列表的容量有上限,而生成器则不需要一下子创建完整的列表,而是一边循环一边计算。
生成器表达式生成了一个生成器对象,但其中的变量是以延迟方式获取,直到调用生成器的__next__()
方法
ge = (2 * i for i in range(3))
print(next(ge)) # 输出:0
print(next(ge)) # 2
print(next(ge)) # 4
print(next(ge)) # StopIteration
直到生成器中没有其他元素时,抛出StopIteration
异常
for
循环可以迭代获取生成器对象中的每个元素,并且不会产生StopIteration
异常
for g in ge:
print(g)
for
循环其实隐含调用了生成器对象的__next__()
方法。
yield
表达式
yield_atom ::= "(" yield_expression ")"
yield_expression ::= "yield" [expression_list | "from" expression]
yield
是定义生成器的另一种方式,包含yield
语句的函数就是一个生成器对象。
调用一个生成器函数,返回的是一个迭代器对象。迭代器Iterator
表示的是一个数据流,迭代器可以被next()
函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration
错误。迭代器控制生成器函数的执行,当函数开始运行,执行到第一个yield
语句时暂停,将yield
表达式后的表达式的值返回给调用者。
def wine():
print('first yield...')
yield 1
print('second yield...')
yield 2
ww = wine()
print(next(ww))
**输出:**
first yield...
1
在生成器函数暂停时,其现阶段的状态都被保存下来,包括生成器函数局部变量当前绑定的值、指令指针、函数内部执行堆栈以及任何异常状态的处理。当生成器函数再次被调用时则直接从上次暂停的yield
表达式处接着运行,直到遇到下一个yield
语句,或者没有遇到yield
语句则运行结束。
print(next(ww))
**输出:**
second yield...
2
需要说明的是,在函数重新运行时,其实上次暂停处的yield
表达式会先接收一个值作为结果,然后才接着运行直到碰到下一个yield
表达式。
如果调用者使用next
函数或者__next__()
方法,则默认返回给yield
表达式None
值;使用send()
方法则传递一个值作为yield
表达式的结果。
def wine():
print('first yield...')
x = yield 1
print(x)
print('second yield...')
yield 2
ww = wine()
print(next(ww))
**输出:**
first yield...
1
此时仅输出yield
表达式返回的值,使用send()
方法继续调用:
print(ww.send('the result of first yield...'))
**输出:**
the result of first yield...
second yield...
2
可以看出,首先输出send()
方法传回yield
表达式的值,然后再继续执行后续的内容直到下一个yield
表达式。
注意:在实例化生成器后如果直接调用send()
方法启动函数,应该使用send(None)
方法,因为此时没有yield表达式可以接收其返回的值。或者在实例化生成器后首先使用next()
函数。
当迭代器数据流还没有执行完但要终止生成器,可以调用close()
方法。为了有效监控迭代器的运行,可以将yiled
表达式用try..except..finally
结构中,在调用close()
前可以执行finally
中的代码
def wine():
try:
print('first yield...')
x = yield 1
print(x)
print('second yield...')
yield 2
except Exception as e:
pass
finally:
print('yield is closed.')
ww = wine()
print(next(ww))
print(ww.close())
**输出:**
first yield...
1
yield is closed.
None
可以看出close()
方法返回None值。close()其实内部它是调用了throw(GeneratorExit)。
return
:生成器中也可以包含return语句,但是不能出现在yield表达式中,当执行到return语句时如果有finally语块则执行,之后会抛出StopIteration异常。不再继续其他的迭代生成。
def wine():
if True:
return 10
else:
yield 2
try:
print(type(wine()))
print(wine().__next__())
except StopIteration as e:
print(e.value)
**输出:**
<class 'generator'>
10
方法总结:
generator.__next__()
:启动或从上个yield表达式处恢复生成器运行;当生成器被__next__()
方法恢复运行时,当前yield
表达式被赋值为None;for
循环和next()
函数都隐式调用__next__()
。generator.send(value)
:恢复并返回值给生成器函数;返回给生成器函数的值将赋予当前的yield
表达式,并向调用者返回下一个yield
表达式产生的值。如果要启动生成器函数,则用send(None)
。generator.throw(type[, value[, traceback]])
:在生成器停止的地方抛出异常并关闭生成器。返回生成器函数生成的下一个值。如果生成器没有生成其他值抛出StopIteration
异常,如果生成器没有捕获任何传入异常或者抛出其他异常,则将异常抛给调用者。generator.close()
:在生成器暂停的地方跳出并关闭生成器。
python3官方文档实例
>>> def echo(value=None):
... print("Execution starts when 'next()' is called for the first time.")
... try:
... while True:
... try:
... value = (yield value)
... except Exception as e:
... value = e
... finally:
... print("Don't forget to clean up when 'close()' is called.")
...
>>> generator = echo(1)
>>> print(next(generator))
Execution starts when 'next()' is called for the first time.
1
>>> print(next(generator))
None
>>> print(generator.send(2))
2
>>> generator.throw(TypeError, "spam")
TypeError('spam',)
>>> generator.close()
Don't forget to clean up when 'close()' is called.
进阶
yield from <expr>
:from后的表达式是一个子迭代器,子迭代器产生的值直接传给当前生成器方法的调用者,send()传进的值都被传给底层迭代器中合适的方法。详细内容将在后续章节继续学习生成器函数和协程
coroutines
很像,协程有多轮yield
表达式,拥有多个程序切入点并且也可以暂停数据流等待下一次被调用。生成器和线程的不同之处在于:生成器在yield
之后无法控制接下来在哪里继续执行,而是需要由生成器的调用者控制。详细内容将在后续章节继续学习
总结:
- yiled所在的函数是生成器
- 调用生成器得到一个迭代器,利用next()或next()不断获取数据
- 调用者使用send方法传给yield表达式一个值,并从下一个yield表达式获取一个值