Преобразование вывода "yield from" в код Python 2.7

У меня был код ниже в 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

Ответ 1

Если вы не используете результаты своих выходов, * вы всегда можете включить это:

yield from foo:

... в это:

for bar in foo:
    yield bar

Там может быть стоимость исполнения **, но никогда не существует семантической разницы.


Записываются ли записи из двух половинок (вверх и вниз) друг к другу в одном списке, чтобы функция родительской рекурсии с выходом из вызова и использовала обе половины вместе?

Нет! Вся суть итераторов и генераторов заключается в том, что вы не создаете фактические списки и не добавляете их вместе.

Но эффект подобен: вы просто уродитесь от одного, а затем урожай от другого.

Если вы думаете о верхней половине и нижней половине как "ленивые списки", то да, вы можете думать об этом как "ленивое приложение", которое создает больший "ленивый список". И если вы вызываете list на результат родительской функции, вы, естественно, получите фактический list, эквивалентный объединению двух списков, которые вы получили бы, если бы вы сделали yield list(…) вместо yield from ….

Но я думаю, что легче думать об этом по-другому: то, что он делает, точно такое же, как и циклы for.

Если вы сохранили два итератора в переменных и зациклились над itertools.chain(upper, lower), это будет то же самое, что и цикл по первому, а затем цикл по второму, правильно? Здесь нет никакой разницы. Фактически, вы могли бы реализовать chain как просто:

for arg in *args:
    yield from arg

* Не значения, которые генерирует генератор для своего вызывающего абонента, значение самих выражений выхода, внутри генератора (который поступает от вызывающего, используя метод send), как описано в PEP 342. Вы не используете их в своих примерах. И я готов поспорить, что вы не в своем реальном коде. Однако код примера в сопрограмме часто используется для выражения примера yield from - см. PEP 380, в котором вводится yield from - поэтому его придется переписать. Но если нет, вы можете использовать PEP также показывает вам полный ужасный грязный эквивалент, и вы можете, конечно, обмануть те части, которые вам не нужны. И если вы не используете значение выражения, оно сводится к двум строкам выше.

** Не огромный, и вы ничего не можете сделать с этим, не используя Python 3.3 или полностью перестраивая свой код. Это в точности тот же случай, что и перевод списков в петли Python 1.5 или любой другой случай, когда есть новая оптимизация в версии X.Y, и вам нужно использовать более старую версию.

Ответ 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

Я думаю, что нашел способ эмуляции конструкции Python 3.x yield from в Python 2.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. Они могут быть добавлены, и я отредактирую свою запись, как только я ее заработаю.

Ответ 4

Замените их на for-loops:

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

Ответ 5

Как насчет использования определения из 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)

за исключением того, что, как и в настоящее время, исключение не может быть захвачено за исключением предложений в возвращающем генераторе.

Исключение StopIteration ведет себя так, как если бы оно было определено следующим образом:

class StopIteration(Exception):

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

Ответ 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()

Обратите внимание, что для операций сложной логики требуется только регистрация в качестве ресурса.