Почему zip() удаляет значения моего генератора?
Я писал ответ на этот вопрос, когда заметил, что моя простая реализация не дала правильных результатов. При поиске ошибки я заметил следующее:
In [1]: import itertools
In [2]: gen = itertools.cycle((0,1,2))
In [3]: zip(gen, range(3))
Out[3]: [(0, 0), (1, 1), (2, 2)]
In [4]: zip(gen, range(3))
Out[4]: [(1, 0), (2, 1), (0, 2)]
По какой-либо причине метод gen
next()
называется одним дополнительным временем.
Чтобы проиллюстрировать это, я использовал следующее:
class loudCycle(itertools.cycle):
def next(self):
n = super(loudCycle, self).next()
print n
return n
In [6]: gen = loudCycle((0,1,2))
In [7]: zip(gen, range(3))
0
1
2
0
Out[7]: [(0, 0), (1, 1), (2, 2)]
Ответы
Ответ 1
Это происходит потому, что zip
оценивает итераторы слева направо, а это означает, что после трех шагов он вызывает next()
на gen
и только затем на iter(range(3))
(или что-то в этом роде) и встречается с StopIteration
. Чтобы обойти это, используйте более короткий (конечный) итерабельный как самый левый аргумент:
In [8]: zip(range(3), gen)
0
1
2
Out[8]: [(0, 0), (1, 1), (2, 2)]
Ответ 2
Ваш собственный ответ является правильным, и представляет собой очень хорошее решение - если один из аргументов zip
всегда короче другого. Однако в ситуациях, когда вы не знаете, что будет короче, вы можете найти islice
полезным. islice
также обеспечивает легкое обходное решение, если вы хотите, чтобы первый элемент в ваших кортежах был из вашего генератора. В вашем случае вы можете сделать это:
>>> import itertools
>>> gen = itertools.cycle(('a', 'b', 'c'))
>>> seq = range(3)
>>> zip(itertools.islice(gen, len(seq)), seq)
[('a', 0), ('b', 1), ('c', 2)]
>>> zip(itertools.islice(gen, len(seq)), seq)
[('a', 0), ('b', 1), ('c', 2)]
Ваш ответ, вероятно, лучше в этом случае - это, конечно, проще - но я думал, что добавлю это как дополнение.