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


у меня был код ниже в Python 3.2, и я хотел запустить его в Python 2.7. Я его преобразовал (поставил код missing_elements в обеих версиях), но я не уверен, что это самый эффективный способ сделать это. В основном, что происходит, если есть два 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
6 53

6 ответов:

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

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

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

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

Я только что столкнулся с этой проблемой, и мое использование было немного сложнее, так как мне нужен возвращаемое значение на 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

надеюсь, это поможет людям, которые столкнулись с той же проблемой. :)

замените их на 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

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

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

Я нашел использование контекстов ресурсов (используя 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()

обратите внимание, что сложные логические операции требуют только регистрации в качестве ресурса.