Я пытаюсь сгладить вложенную генератор генераторов, но я получаю неожиданный результат:Сведения вложенной генератор выражений
>>> g = ((3*i + j for j in range(3)) for i in range(3))
>>> list(itertools.chain(*g))
[6, 7, 8, 6, 7, 8, 6, 7, 8]
Я ожидал, что результат выглядит следующим образом:
[0, 1, 2, 3, 4, 5, 6, 7, 8]
Я думаю, что получаю неожиданный результат, потому что внутренние генераторы не оцениваются до тех пор, пока внешний генератор уже не будет повторен, установив i
на 2. Я могу взломать решение, заставив оценку внутренних генераторов, используя понимание списка вместо генного Выражение Erator:
>>> g = ([3*i + j for j in range(3)] for i in range(3))
>>> list(itertools.chain(*g))
[0, 1, 2, 3, 4, 5, 6, 7, 8]
В идеале я хотел бы решение, которое полностью ленивый и не принуждать оценку внутренних вложенных элементов, пока они не используются.
Есть ли способ сгладить вложенные выражения генератора произвольной глубины (возможно, используя что-то, кроме itertools.chain
)?
Edit:
Нет, мой вопрос не является дубликатом Variable Scope In Generators In Classes. Я честно не могу сказать, как эти два вопроса связаны вообще. Может быть, модератор мог бы объяснить, почему он думает, что это дубликат.
Кроме того, оба ответа на мой вопрос верны в том, что они могут использоваться для записи функции, которая правильно выравнивает вложенные генераторы.
def flattened1(iterable):
iter1, iter2 = itertools.tee(iterable)
if isinstance(next(iter1), collections.Iterable):
return flattened1(x for y in iter2 for x in y)
else:
return iter2
def flattened2(iterable):
iter1, iter2 = itertools.tee(iterable)
if isinstance(next(iter1), collections.Iterable):
return flattened2(itertools.chain.from_iterable(iter2))
else:
return iter2
Насколько я могу сказать с timeit
, они оба выполняют одинаково.
>>> timeit(test1, setup1, number=1000000)
18.173431718023494
>>> timeit(test2, setup2, number=1000000)
17.854709611972794
Я не уверен, какой из них лучше с точки зрения стиля либо, так как это x for y in iter2 for x in y
немного обманщик мозга, но, возможно, более элегантно, чем itertools.chain.from_iterable(iter2)
. Вход оценивается.
К сожалению, я был в состоянии только отметить один из двух одинаково хороших ответов.
+1 Это действительно подлый и действительно классный, но это не помогает мне сгладить вложенный генератор, с которого я начал работать. –
Нет, я думаю, ты прав! Хотя, если вы строите вложенные генераторы таким образом, чтобы значения внутренних генераторов зависели от времени оценки внешних генераторов, это программируемая бомба замедленного действия - некоторая мелкая рефакторинг кода приведет к взрыву всего. Может быть хорошей идеей перепроектировать внутренние генераторы, чтобы избежать нежелательного захвата переменной, независимо от того, как вы решили сгладить 'g' в конце. –
Все, что вы сказали, верно, но моя (неустановленная) цель состоит не в том, чтобы использовать генераторы, подобные этим везде ('range()' генерирует сглаженные диапазоны чисел просто отлично), но для того, чтобы другие программисты могли определять многомерные массивы с использованием генераторов. Если они думают, что их многомерный массив выглядит как [[0,1], [2,3]], когда на самом деле он выглядит как [[2,3], [2,3]] - все без каких-либо ошибок - то я прищурился. –