zoukankan      html  css  js  c++  java
  • Python: generator, yield, yield from 详解

    1.Generator Expressions

    生成器表达式是用小括号表示的简单生成器标记法:

    generator_expression ::= "(" expression comp_for ")"

    生成器表达式产生一个生成器对象,它的语法和for类似,出了它是被“()”包含,而不是[]或{};

    生成器表达式中变量的计算被延迟到__next__()函数的调用,然而最左边for循环子句被立即计算,这样,如果他有错误的话可以被立即看到。后面的for循环子句不能被立即计算,因为他们可能依赖于前面的for循环,例如(x*y for in range(10) for in bar(x)

    python3.6以后,如果生成器出现在async def function中,那么async for子句和await表达式可以被理解为是异步的。如果生成器表达式包含async for子句或者await表达式,就叫做异步生成器表达式。异步生成器表达式产生一个新的异步生成器对象,它是一个异步迭代器。

     

    2. Yield Expressions

    yield_atom       ::=  "(" yield_expression ")"
    yield_expression ::=  "yield" [expression_list | "from" expression]

    Yield表达式用于定义一个生成器函数或异步生成器函数,因此只能被用于一个函数定义体内。在一个函数定义体中使用yield表达式使其成为生成器,
    在一个async def函数体内使用yield表达式使协程函数成为一个异步生成器。例如:
    def gen(): //定义一个生成器函数
      yield 123
    async def agen(): //定义一个异步生成器函数
      yield 123

    当一个生成器函数被调用,它返回一个迭代器,也叫生成器。这个生成器控制生成器函数的执行。当生成器的一个函数被调用的时候,生成器函数开始执行。
    执行到第一个yield表达式时,挂起,返回expression_list的值给调用者。对于挂起,我们指的是所有局部状态被保留,包含当前局部变量的绑定,
    指令指针,内部调用栈,任何异常处理状态。当通过调用生成器的一个函数恢复执行时,函数的执行就好像是从外部再次调用yield表达式一样。恢复执行后,
    yield表达式的值取决调用的方法。如果是__next__()被调用(一般通过for循环或者内置的next()函数),那么值是None。如果是send()被调用,
    值是传给send的参数的值。

    所有的这些使得生成器函数非常像协程;它产生多次值,有多个入口点并且执行可以被挂起。唯一的不同是,生成器函数yield后不能控制程序从哪里继续执行,
    控制权总是传回给生成器的调用者。

    yield表达式可以在try块的任何地方,如果生成器在被结束(到达零引用或者因为垃圾回收机制)之前没有被恢复执行,生成器的close函数被调用,因此finally
    子句被执行。
    当yield from <expr> 被使用,它把附加的表达式当成一个子迭代器,所有子迭代器产生的值被直接传回给当前生成器函数的调用者。当前生成器调用send的参数值
    和调用throw的异常参数 都将被传给底层迭代器(子迭代器),如果他有对应的方法的话。否则,send导致AttributeError或者TypeError,而throw立即
    raise传给他的异常。
    当底层迭代器执行完成,StopIteration对象的value值变成这个yield from表达式的值。这个值可以被显式的设置当产生StopIteration时,或者自动设置,如果
    子迭代器是一个生成器(子生成器返回一个值)
    3. 生成器-迭代器 方法
    这部分介绍生成器迭代器的方法,他们可以被用来控制生成器函数的执行。当生成器正在执行时调用这些函数将导致ValueError。
    (1)generator.__next__()
    开始生成器函数的执行或者从最后被执行的yield表达式中恢复执行,如果是恢复执行,yield表达式的值是None,继续执行到下一个yield表达式,
    挂起,返回expression_list的值给__next__()的调用者。如果生成器没有再yield一个值,则产生一个StopIteration异常
    这个方法一般被隐式调用,例如for循环,next()
    (2)generator.send(value)
    恢复执行并且发送一个值到生成器函数。这个value参数就是当前yield表达式的结果。send方法返回下一个生成器yield的值
    如果没有yield,返回StopIteration。当用send来启动生成器,他的参数必须是None,因为没有yield表达式接收值。
    (3)generator.throw(type[,value[,traceback]])
    在生成器挂起的地方产生一个type类型的异常,并且返回下一个生成器函数yield的值,如果没有yield,返回StopIteration。
    如果生成器函数没有捕获这个传进去的异常 ,或者产生了另一个不同的异常,那么将这个异常传递给调用者。
    (4)generator.close()
    在生成器挂起的地方产生一个GeneratorExit(),如果生成器之后优雅的退出,已经关闭,或者产生了一个GeneartorExit(不捕获该异常),close将返回到它的
    调用者。如果生成器yield一个值,那么产生一个RuntimeError。如果生成器产生了任何其他异常,将传递给调用者。如果
    生成器因为一个异常或者正常退出,那么close不做任何事情。
    实例:
    >>> 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.

    PEP380 加入了yield from表达式,允许一个生成器委派部分操作给另一个生成器。这可以剔除一部分包含yield的代码放到另一个生成器。另外,
    子生成器可以返回一个值,这个值对于委托生成器也是可用的。
    虽然主要涉及用来委派一个子生成器,但是yield from 表达式事实上可以委派任何的子迭代器。
    低于简单的迭代器, yield from iterable 本质上就是一个简短的形式:for item in iterable: yield item,例如:
    >>> def g(x):
    ...     yield from range(x, 0, -1)
    ...     yield from range(x)
    ...
    >>> list(g(5))
    [5, 4, 3, 2, 1, 0, 1, 2, 3, 4]
    然而,不像普通的循环,yield from 允许子生成器直接从调用区域接收send和throw的值,并且返回一个最后的值给外层生成器。示例如下:
    >>> def accumulate():
    ...     tally = 0
    ...     while 1:
    ...         next = yield
    ...         if next is None:
    ...             return tally
    ...         tally += next
    ...
    >>> def gather_tallies(tallies):
    ...     while 1:
    ...         tally = yield from accumulate()
    ...         tallies.append(tally)
    ...
    >>> tallies = []
    >>> acc = gather_tallies(tallies)
    >>> next(acc)  # Ensure the accumulator is ready to accept values
    >>> for i in range(4):
    ...     acc.send(i)
    ...
    >>> acc.send(None)  # Finish the first tally
    >>> for i in range(5):
    ...     acc.send(i)
    ...
    >>> acc.send(None)  # Finish the second tally
    >>> tallies
    [6, 10]
     




     
  • 相关阅读:
    codeforces 505E Mr. Kitayuta vs. Bamboos 题解
    codeforces 568C New Language 题解
    [AGC020E] Encoding Subsets 题解
    技巧瞎扯
    [AGC028C] Min Cost Cycle 题解
    [AGC018D] Tree and Hamilton Path 题解
    codeforces 1217D Coloring Edges 题解
    [AGC003C] BBuBBBlesort! 题解
    [AGC037C] Numbers on a Circle 题解
    [USACO09Open] Tower of Hay 题解
  • 原文地址:https://www.cnblogs.com/programmer-wfq/p/7173580.html
Copyright © 2011-2022 走看看