Преобразование вывода "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()
Обратите внимание, что для операций сложной логики требуется только регистрация в качестве ресурса.