Вложенные генераторные выражения ведут себя неожиданно
Со следующим кодом:
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
. Если вместо этого я использовал понимание списка, проблема исчезнет. Почему это?
Ответы
Ответ 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))