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.

进阶

  1. yield from <expr>:from后的表达式是一个子迭代器,子迭代器产生的值直接传给当前生成器方法的调用者,send()传进的值都被传给底层迭代器中合适的方法。详细内容将在后续章节继续学习

  2. 生成器函数和协程coroutines很像,协程有多轮yield表达式,拥有多个程序切入点并且也可以暂停数据流等待下一次被调用。生成器和线程的不同之处在于:生成器在yield之后无法控制接下来在哪里继续执行,而是需要由生成器的调用者控制。详细内容将在后续章节继续学习

总结:

  1. yiled所在的函数是生成器
  2. 调用生成器得到一个迭代器,利用next()或next()不断获取数据
  3. 调用者使用send方法传给yield表达式一个值,并从下一个yield表达式获取一个值
参考文献:

python3官方文档
廖学峰的官方网站-python3教程

results matching ""

    No results matching ""