将“ yield from”语句转换为Python 2.7代码


73

我在Python 3.2中有一个下面的代码,我想在Python 2.7中运行它。我确实进行了转换(missing_elements在两个版本中都放了代码),但是我不确定这是否是最有效的方法。基本上,如果函数的yield from上半部和下半部有两个类似下面的调用,会发生什么情况missing_element?是否将两个半部分(上半部分和下半部分)中的条目彼此追加到一个列表中,以便父递归函数与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-froms等)。我在PyPI中发布了一个程序包,试图对行为进行全面介绍:amir.rachum.com/yieldfrom
Amir Rachum,

Answers:


93

如果您不使用收益率*,则可以随时执行以下操作:

yield from foo

…进入:

for bar in foo:
    yield bar

可能会有性能成本**,但从语义上讲永远不会有差异。


是否将两个半部分(上半部分和下半部分)中的条目彼此追加到一个列表中,以使父递归函数与call的收益并同时使用这两个半部分?

没有!迭代器和生成器的全部要点是,您无需构建实际的列表并将它们附加在一起。

但是效果是相似的:您只是从一种收益,然后从另一种收益。

如果您将上半部分和下半部分视为“懒惰列表”,则可以,您可以将其视为“懒惰附加”,它会创建一个较大的“懒惰列表”。如果你调用list父函数的结果,当然,你得到一个实际list这相当于,如果你做这些,你会得到两个列表附加在一起yield list(…),而不是yield from …

但是我认为反过来更容易想到:它所做的与for循环所做的完全相同。

如果将两个迭代器保存到变量中并进行循环itertools.chain(upper, lower),则与在第一个循环然后在第二个循环上进行相同,对吗?没什么区别。实际上,您可以实现chain如下:

for arg in *args:
    yield from arg

*不是生成器产生给调用者的值,而是生成器内部的yield表达式本身的值(使用send方法从调用者产生),如PEP 342中所述。您没有在示例中使用这些。而且我敢打赌您不在您的真实代码中。但是协程式代码通常使用yield from表达式的值-有关示例,请参阅PEP 3156。此类代码通常取决于Python 3.3生成器的其他功能-特别是StopIteration.value来自引入了同一PEP 380的新功能yield from—因此将不得不重写。但是,如果没有,您可以使用PEP还向您显示完整的混乱杂物,当然您也可以减少不需要的部分。而且,如果您不使用表达式的值,它将缩减为上面的两行。

**数量不多,除了使用Python 3.3或完全重组代码外,您无能为力。这与将列表解析转换为Python 1.5循环完全相同,或者在XY版本中有新的优化并且您需要使用旧版本时,则为其他情况。


一个问题,递归调用如何工作?父函数的“ yield from”函数是否在子级中合并了两个“ yield from”语句。如果不连续,则低:从missing_elements(L,start,index)的收益#上部是连续的吗?Continuous_high = L [index] == L [end]-(结束-索引),如果不是
continuous_high

1
@ vkaul11:它的工作原理与循环完全相同,只是速度更快(并支持循环无法执行的各种更复杂的情况)。如果需要详细信息,请阅读PEP。
abarnert

@abarnet代码是否未使用后续递归调用中yield的结果?我试图在工作中使用某人的代码并在Python 2.7中运行,所以想了解您为什么这么说。“ recursive_function()的收益”是否只是在recursive_function中的每个嵌套收益上循环
vkaul11

@ vkaul11:不是生成器产生给调用者的值,而是生成器中实际yield表达式的值。这很难解释。如果您想了解它,请参阅PEP 342,但要简要介绍一下:如果您从不调用send生成器,或者从不foo = (yield bar)在生成器内部进行操作,并且无法想象为什么要这样做,请……不要担心。您有时间阅读PEP 342(以及380和3156以及Greg Ewing的漂亮博客文章,链接自3156)。
abarnert

1
@trss:不,还是一样。两种形式都不起作用。比较这个这个; 它们都没有产生任何结果,所以先打印出来done然后提高StopIteration。(尽管没有产生任何结果,它们都将函数标记为生成器函数,这意味着您可以yield from ()代替if False: yield None强制生成器。)
abarnert 2014年

7

我只是碰到这个问题,我使用的是一个有点困难,因为我需要返回值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

希望这将对遇到相同问题的人们有所帮助。:)


6

用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

3

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

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中描述的各种特殊情况。可以添加这些,一旦我使用它,我将编辑我的条目。


6
与abernert较早的简单解决方案(转换为for循环)相比,该解决方案有什么优势?
ToolmakerSteve

这需要是一个ActiveState配方。
kirbyfan64sos 2014年

很好的实现。只需说明Trollius项目(适用于Python <3.3的asyncio)使用一种From方法即可完成相同的工作。它的实施肯定已经准备好生产。
jsbueno

非常感谢,它应该是公认的答案。我知道我可以使用abarnert的方式做到这一点。但是我想找到更有效的方法来做到这一点,这就是为什么我来到此页面。
yunfan

3

如何使用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)

0

我发现使用资源上下文(使用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可以同时yield fromreturn和代替。
塔德格·麦当劳-詹森
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.