Когда мне следует вручную вызывать `StopIteration` с помощью пользовательских генераторов python? - PullRequest
0 голосов
/ 10 октября 2019

В следующем коде я предполагаю, что у меня есть два генератора, дающие отсортированные и сопоставимые значения, и я хочу создать генератор, который выдает «синхронизированные» пары из двух. Под синхронизацией я подразумеваю отдачу от обоих, когда они дают одно и то же значение, в противном случае опережая только «задержанное», (в сочетании с тем, что оно дает с 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 хорошо исправляет мой код. Мне все еще интересны некоторые объяснения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...