Подтвердить что ты не робот

Вложенные генераторные выражения ведут себя неожиданно

Со следующим кодом:

A = [1, 2]
B = [-2, -1]
C = [-1, 2]
D = [0, 2]

ab = (a + b for a in A for b in B)
cd = (c + d for c in C for d in D)
abcd = (e_ab + e_cd for e_ab in ab for e_cd in cd)

Ожидается len(abcd) 16, но на самом деле это 4. Если вместо этого я использовал понимание списка, проблема исчезнет. Почему это?

4b9b3361

Ответ 1

Вы можете ездить только на поезде генератора один раз, после того, как он достигнет цели, больше не едет. В вашем случае генератор cd исчерпан, а затем снова не может быть повторен.

list объекты, с другой стороны, создают отдельный объект-итератор каждый раз, когда вы вызываете iter на них (который цикл for неявно делает для вас):

print(iter([1, 2, 3]))
# <list_iterator at 0x7f18495d4c88> 

и создайте свежий итератор, который вы можете использовать. Это происходит в любое время, когда на него вызывается iter; так как каждый новый объект создается каждый раз, вы можете просматривать списки несколько раз. Несколько поездок!

Короче говоря, если вы только изменяете cd как список (в общем, объект, который будет повторяться через несколько раз):

ab = (a + b for a in A for b in B)
cd = [c + d for c in C for d in D]  # list-comp instead

он даст желаемый результат, создав свежие объекты итератора из cd для каждого элемента в ab:

abcd = (e_ab + e_cd for e_ab in ab for e_cd in cd)
print(len(list(abcd)))
# 16

конечно, вы можете достичь этого, используя product из itertools тоже, но, что не в порядке, почему это происходит.

Ответ 2

Я думаю, это потому, что вы можете только перебирать генератор один раз. Поэтому после того, как вы впервые зациклились на e_cd, это ничего не произведет на другой итерации внешнего цикла.

Ответ 3

Если генератор не имеет дополнительных значений для возврата, он вызывает исключение StopIteration. Вот как они сигнализируют, что они сделаны. Поскольку для генераторов reset нет встроенного способа генерации многоэтапного генератора из генераторов, он останавливается при первом встреченном StopIteration, а не вызывает генерации генераторов-потомков, как это происходит со списком- как объекты.

itertools.product() может давать желаемые результаты (repl.it здесь):

import itertools

A = [1, 2]
B = [-2, -1]
C = [-1, 2]
D = [0, 2]

ab = (a + b for a in A for b in B)
cd = (c + d for c in C for d in D)
abcd = (e_ab + e_cd for e_ab, e_cd in itertools.product(ab,cd))