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

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. Если бы вместо этого я использовал понимание списка, проблема исчезнет. Это почему?

21
Zhe Chen 10 Янв 2017 в 08:00

3 ответа

Лучший ответ

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

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

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

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

Короче говоря, если вы only измените 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, но это не является причиной того, почему это происходит.

27
Dimitris Fasarakis Hilliard 3 Мар 2017 в 11:46

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

itertools.product() может дать желаемые результаты (ответ .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))
12
Ouroborus 10 Янв 2017 в 16:53

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

16
neverwalkaloner 10 Янв 2017 в 05:16