В следующем коде я предполагаю, что у меня есть два генератора, дающие отсортированные и сопоставимые значения, и я хочу создать генератор, который выдает «синхронизированные» пары из двух. Под синхронизацией я подразумеваю отдачу от обоих, когда они дают одно и то же значение, в противном случае опережая только «задержанное», (в сочетании с тем, что оно дает с None
).
from itertools import repeat
def generate_pairs(g1, g2):
try:
n1 = next(g1)
except StopIteration:
yield from zip(repeat(None), g2)
# A
# raise StopIteration
try:
n2 = next(g2)
except StopIteration:
yield from zip(g1, repeat(None))
# A
# raise StopIteration
while True:
if n1 > n2:
yield (None, n2)
try:
n2 = next(g2)
except StopIteration:
yield (n1, None)
yield from zip(g1, repeat(None))
# B
# raise StopIteration
elif n1 < n2:
yield (n1, None)
try:
n1 = next(g1)
except StopIteration:
yield (None, n2)
yield from zip(repeat(None), g2)
# B
# raise StopIteration
else:
yield (n1, n2)
try:
n1 = next(g1)
except StopIteration:
yield from zip(repeat(None), g2)
# C
# raise StopIteration
try:
n2 = next(g2)
except StopIteration:
yield from zip(g1, repeat(None))
# C
# raise StopIteration
Где я должен явно поднять StopIteration
?
В приведенном выше состоянии, когда я пытаюсь использовать уже синхронизированные генераторы, я вижу, что повышение в случае C требуется.
pairs = generate_pairs((n1 for n1 in [1, 2, 3]), (n2 for n2 in [1, 2, 3]))
Вышеприведенное может привести к получению последней пары (3, 3)
навсегда:
from cytoolz import take
list(take(10, pairs))
Вывод:
[(1, 1),
(2, 2),
(3, 3),
(3, 3),
(3, 3),
(3, 3),
(3, 3),
(3, 3),
(3, 3),
(3, 3)]
В B тоже, похоже, ручное StopIteration
должно быть повышено:
pairs = generate_pairs((n1 for n1 in [1, 3]), (n2 for n2 in [1, 2]))
list(take(10, pairs))
Вывод:
[(1, 1),
(None, 2),
(3, None),
(None, 2),
(3, None),
(None, 2),
(3, None),
(None, 2),
(3, None),
(None, 2)]
И из приведенного ниже теста мне кажется, что для А также требуется какой-то способ завершения генератора:
pairs = generate_pairs((_ for _ in []), (n2 for n2 in [1, 2, 3]))
list(take(10, pairs))
Выход:
UnboundLocalError Traceback (most recent call last)
<ipython-input-96-61eb4df81d52> in <module>
----> 1 list(take(10, pairs))
<string> in generate_pairs(g1, g2)
UnboundLocalError: local variable 'n1' referenced before assignment
Однако, если я раскомментирую все raise StopIteration
в коде, мне нужно обработать возникающие исключения вручную. Например, они не обрабатываются автоматически для циклов.
Я просто хотел бы, чтобы мой генератор пар прекратил генерировать вещи, как только оба входных генератора были исчерпаны, без драмы. В чем я ошибся?
Редактировать
Кажется, что использование return
вместо raise StopIteration
хорошо исправляет мой код. Мне все еще интересны некоторые объяснения.