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