【问题标题】:Converting "yield from" statement to Python 2.7 code将“yield from”语句转换为 Python 2.7 代码
【发布时间】:2013-07-09 00:20:01
【问题描述】:

我在 Python 3.2 中有一段代码,我想在 Python 2.7 中运行它。我确实转换了它(在两个版本中都放入了missing_elements 的代码),但我不确定这是否是最有效的方法。基本上如果在missing_element 函数的上半部分和下半部分有两个yield from 调用,会发生什么情况?两半(上半和下半)的条目是否在一个列表中相互附加,以便父递归函数与 yield from 调用一起使用两半?

def missing_elements(L, start, end):  # Python 3.2
    if end - start <= 1: 
        if L[end] - L[start] > 1:
            yield from range(L[start] + 1, L[end])
        return

index = start + (end - start) // 2

# is the lower half consecutive?
consecutive_low =  L[index] == L[start] + (index - start)
if not consecutive_low:
    yield from missing_elements(L, start, index)

# is the upper part consecutive?
consecutive_high =  L[index] == L[end] - (end - index)
if not consecutive_high:
    yield from missing_elements(L, index, end)

def main():
    L = [10, 11, 13, 14, 15, 16, 17, 18, 20]
    print(list(missing_elements(L, 0, len(L)-1)))
    L = range(10, 21)
    print(list(missing_elements(L, 0, len(L)-1)))

def missing_elements(L, start, end):  # Python 2.7
    return_list = []                
    if end - start <= 1: 
        if L[end] - L[start] > 1:
            return range(L[start] + 1, L[end])

    index = start + (end - start) // 2

    # is the lower half consecutive?
    consecutive_low =  L[index] == L[start] + (index - start)
    if not consecutive_low:
        return_list.append(missing_elements(L, start, index))

    # is the upper part consecutive?
    consecutive_high =  L[index] == L[end] - (end - index)
    if not consecutive_high:
        return_list.append(missing_elements(L, index, end))
    return return_list

【问题讨论】:

  • 下面的大多数实现在某些方面都缺乏支持(用于向生成器发送值、处理嵌套的 yield-from 等)。我在 PyPI 中发布了一个包,试图在行为上做到全面:amir.rachum.com/yieldfrom

标签: python generator python-2.x yield yield-from


【解决方案1】:

如果你不使用你的收益结果,*你可以总是转这个:

yield from foo

…进入这个:

for bar in foo:
    yield bar

可能会有性能成本,**但绝不会有语义差异。


两半(上半和下半)的条目是否在一个列表中相互附加,以便父递归函数使用 yield from call 并同时使用两半?

不!迭代器和生成器的全部意义在于您无需构建实际列表并将它们附加在一起。

效果是相似的:你只是从一个屈服,然后从另一个屈服。

如果您将上半部分和下半部分视为“惰性列表”,那么是的,您可以将其视为创建更大“惰性列表”的“惰性追加”。如果你在父函数的结果上调用list,你当然得到一个实际的list,这相当于将你完成@的两个列表附加在一起。 987654331@ 而不是 yield from …

但我认为反过来想起来更容易:它的作用与for 循环的作用完全相同。

如果您将两个迭代器保存到变量中,然后循环遍历itertools.chain(upper, lower),这与循环遍历第一个然后遍历第二个是一样的,对吧?这里没有区别。实际上,您可以将chain 实现为:

for arg in *args:
    yield from arg

* 如PEP 342 中所述,不是生成器向其调用者产生的值,而是生成器内的yield 表达式本身的值(来自使用send 方法的调用者)。您没有在示例中使用这些。我敢打赌你不在你的真实代码中。但是协程风格的代码经常使用yield from 表达式的值——参见PEP 3156 的例子。此类代码通常依赖于 Python 3.3 生成器的其他特性——特别是来自引入 yield from 的同一 PEP 380 的新 StopIteration.value——因此必须重写。但如果没有,您可以使用 PEP 还向您展示完整的可怕的混乱等价物,您当然可以削减您不关心的部分。如果你不使用表达式的值,它会减少到上面的两行。

** 不是很大,除了使用 Python 3.3 或完全重组代码之外,您无能为力。这与将列表推导转换为 Python 1.5 循环或任何其他在 X.Y 版本中有新优化并且您需要使用旧版本的情况完全相同。

【讨论】:

  • 一个问题递归调用是如何工作的? “yield from”父函数是否在子函数中结合了两个“yield from”语句。 if not Continuous_low: yield from missing_elements(L, start, index) # 上半部分是连续的吗? Continuous_high = L[index] == L[end] - (end - index) 如果不是 Continuous_high:从 missing_elements(L, index, end) 获得收益
  • @vkaul11:它的工作原理与循环完全一样,只是速度更快(并且支持循环无法实现的各种更复杂的情况)。如果您想了解血淋淋的细节,请阅读 PEP。
  • @abarnet 代码是否没有在后续递归调用中使用 yield from 的结果?我试图在工作中使用某人的代码并在 Python 2.7 中运行,所以想了解您为什么这么说。 “recursive_function() 的收益”是否只是循环遍历 recursive_function 中的每个嵌套收益
  • @vkaul11:不是生成器产生给调用者的值,而是生成器中实际的yield 表达式的值。这很难解释;如果您想了解它,请参阅PEP 342,但简单地说:如果您从未在生成器上调用send,或者从未在生成器内部调用foo = (yield bar),并且无法想象您为什么想要这样做……在您有时间阅读 PEP 342(以及 380 和 3156 以及 Greg Ewing 从 3156 链接的漂亮博客文章)之前,请不要担心。
  • @trss:不,还是一样。两种形式都无济于事。比较thisthis;他们都没有产生任何结果,所以他们打印出done然后提高StopIteration。 (尽管没有产生任何结果,但它们都将该函数标记为生成器函数,这意味着您可以yield from () 而不是if False: yield None 来强制生成器。)
【解决方案2】:

我刚遇到这个问题,我的使用有点困难,因为我需要yield from返回值

result = yield from other_gen()

这不能表示为一个简单的for 循环,但可以用这个来重现:

_iter = iter(other_gen())
try:
    while True: #broken by StopIteration
        yield next(_iter)
except StopIteration as e:
    if e.args:
        result = e.args[0]
    else:
        result = None

希望这能帮助遇到同样问题的人。 :)

【讨论】:

    【解决方案3】:

    用 for 循环替换它们:

    yield from range(L[start] + 1, L[end])
    
    ==>
    
    for i in range(L[start] + 1, L[end]):
        yield i
    

    元素也一样:

    yield from missing_elements(L, index, end)
    
    ==>
    
    for el in missing_elements(L, index, end):
        yield el
    

    【讨论】:

      【解决方案4】:

      如何使用来自pep-380 的定义来构造 Python 2 语法版本:

      声明:

      RESULT = yield from EXPR
      

      在语义上等价于:

      _i = iter(EXPR)
      try:
          _y = next(_i)
      except StopIteration as _e:
          _r = _e.value
      else:
          while 1:
              try:
                  _s = yield _y
              except GeneratorExit as _e:
                  try:
                      _m = _i.close
                  except AttributeError:
                      pass
                  else:
                      _m()
                  raise _e
              except BaseException as _e:
                  _x = sys.exc_info()
                  try:
                      _m = _i.throw
                  except AttributeError:
                      raise _e
                  else:
                      try:
                          _y = _m(*_x)
                      except StopIteration as _e:
                          _r = _e.value
                          break
              else:
                  try:
                      if _s is None:
                          _y = next(_i)
                      else:
                          _y = _i.send(_s)
                  except StopIteration as _e:
                      _r = _e.value
                      break
      RESULT = _r
      

      在生成器中,语句:

      return value
      

      在语义上等价于

      raise StopIteration(value)
      

      除了目前,返回的生成器中的except 子句无法捕获异常。

      StopIteration 异常的行为就像是这样定义的:

      class StopIteration(Exception):
      
          def __init__(self, *args):
              if len(args) > 0:
                  self.value = args[0]
              else:
                  self.value = None
              Exception.__init__(self, *args)
      

      【讨论】:

        【解决方案5】:

        我想我找到了一种在 Python 2.x 中模拟 Python 3.x yield from 构造的方法。它效率不高,而且有点hacky,但它是:

        import types
        
        def inline_generators(fn):
            def inline(value):
                if isinstance(value, InlineGenerator):
                    for x in value.wrapped:
                        for y in inline(x):
                            yield y
                else:
                    yield value
            def wrapped(*args, **kwargs):
                result = fn(*args, **kwargs)
                if isinstance(result, types.GeneratorType):
                    result = inline(_from(result))
                return result
            return wrapped
        
        class InlineGenerator(object):
            def __init__(self, wrapped):
                self.wrapped = wrapped
        
        def _from(value):
            assert isinstance(value, types.GeneratorType)
            return InlineGenerator(value)
        

        用法:

        @inline_generators
        def outer(x):
            def inner_inner(x):
                for x in range(1, x + 1):
                    yield x
            def inner(x):
                for x in range(1, x + 1):
                    yield _from(inner_inner(x))
            for x in range(1, x + 1):
                yield _from(inner(x))
        
        for x in outer(3):
            print x,
        

        产生输出:

        1 1 1 2 1 1 2 1 2 3
        

        也许有人觉得这很有帮助。

        已知问题:缺乏对 send() 和 PEP 380 中描述的各种极端情况的支持。这些可以添加,一旦我开始工作,我将编辑我的条目。

        【讨论】:

        • 与 abernert 早期的简单解决方案相比,此解决方案的优势是什么,其中转换为 for 循环?
        • 这需要是一个 ActiveState 配方。
        • 很好的实现。只是要求 Trollius 项目(asyncio for Python From 方法。它的实现肯定已经准备好生产了。
        • 非常感谢,这应该是公认的答案。我知道我可以用 abarnert 的方式做到这一点。但我想找到更有效的方法来做到这一点,这就是我来到这个页面的原因。
        【解决方案6】:

        我发现使用资源上下文(使用python-resources 模块)是在 Python 2.7 中实现子生成器的一种优雅机制。方便的是,无论如何我已经在使用资源上下文了。

        如果在 Python 3.3 中你会:

        @resources.register_func
        def get_a_thing(type_of_thing):
            if type_of_thing is "A":
                yield from complicated_logic_for_handling_a()
            else:
                yield from complicated_logic_for_handling_b()
        
        def complicated_logic_for_handling_a():
            a = expensive_setup_for_a()
            yield a
            expensive_tear_down_for_a()
        
        def complicated_logic_for_handling_b():
            b = expensive_setup_for_b()
            yield b
            expensive_tear_down_for_b()
        

        在 Python 2.7 中,您将拥有:

        @resources.register_func
        def get_a_thing(type_of_thing):
            if type_of_thing is "A":
                with resources.complicated_logic_for_handling_a_ctx() as a:
                    yield a
            else:
                with resources.complicated_logic_for_handling_b_ctx() as b:
                    yield b
        
        @resources.register_func
        def complicated_logic_for_handling_a():
            a = expensive_setup_for_a()
            yield a
            expensive_tear_down_for_a()
        
        @resources.register_func
        def complicated_logic_for_handling_b():
            b = expensive_setup_for_b()
            yield b
            expensive_tear_down_for_b()
        

        注意复杂逻辑操作如何只需要注册为资源。

        【讨论】:

        • 如果您的生成器所做的唯一事情是yield from 另一个生成器(总是一次),那么您可以只返回该生成器。 get_a_thing 可以用 return 替换 yield from,它也可以正常工作。
        猜你喜欢
        • 2022-01-01
        • 2017-01-17
        • 1970-01-01
        • 1970-01-01
        • 2014-05-28
        • 2013-12-16
        • 1970-01-01
        • 1970-01-01
        • 2017-06-01
        相关资源
        最近更新 更多