Возврат и остановка изменения в пользовательских генераторах Python

Ознакомление с некоторыми новыми взглядами на стиль в Python и переход к переходу от StopIteration к return в генераторы. Мой главный вопрос, как именно это должно работать в собственном генераторе. У меня есть класс, где я __next__ метод __next__ напрямую, так как есть некоторая логика, которую я должен добавить для генерации трека.

Ниже довольно близко к тому, что я делаю, есть все критические элементы. Обратите внимание, что я на самом деле не просто создаю генератор, который копирует список, я просто делаю это минимальным примером.

class Test():
    items = <list>

    def __init__(self):
        self.index = 0

    def __next__(self):
        if self.index >= len(self.items):
            raise StopIteration
        value = self.items[self.index]
        self.index += 1
        return value

    def reset(self):
        self.index = 0

Так что в таком случае я перебираю список до тех пор, пока он не будет исчерпан, а затем последующие вызовы решат, сбросить ли генератор или продолжить после того, как он исчерпан. Тем не менее, как я могу включить что-то подобное без использования StopIteration поскольку это устарело? Стандартный совет использования return здесь для поднятия StopIteration , похоже, не применим, и я бы действительно не хотел менять нисходящий код для проверки генератора, дающего Nones

Так что мне здесь делать? StopIteration ли исключения __next__ в __next__ ?

Всего 2 ответа


StopIteration не считается устаревшей, вы просто неправильно поняли, что такое генераторы. На самом деле у вас нет генератора, у вас есть итератор . Генераторы - это просто функции, которые используют yield для создания итератора.

Вы реализуете свою собственную реализацию итератора без использования генераторов. Итераторы поднимают StopIterator из __next__ когда они сделаны. Ваш код правильно делает это здесь.

Из раздела « Типы генераторов » документации по модели данных Python:

Генераторы Python предоставляют удобный способ реализации протокола итератора. Если метод __iter__() объекта контейнера реализован как генератор, он автоматически вернет объект итератора (технически объект генератора), предоставляющий __iter__() и __next__() . Более подробную информацию о генераторах можно найти в документации по выражению yield.

И из той же документации, в разделе « Типы итераторов » :

iterator.__next__()
Верните следующий товар из контейнера. Если больше нет предметов, StopIteration исключение StopIteration .

Существует устаревшее место, но это касается только использования StopIteration в функциях генератора. См. PEP 479 - Изменение обработки StopIteration внутри генераторов :

Этот PEP предлагает изменение для генераторов: когда StopIteration вызывается внутри генератора, он заменяется на RuntimeError . (Точнее, это происходит, когда исключение собирается выпасть из фрейма стека генератора.) Поскольку изменение обратно несовместимо, функция изначально вводится с помощью оператора __future__ .

Внутри генератора использование return вызывает исключение StopIteration . Поднятие StopIteration вручную может на самом деле создавать неясные ошибки, потому что любой потребитель получающегося в результате итератора не может различить правильное использование исключения и неправильные, случайные исключения StopIteration, исходящие из вашего генератора. Это затрудняет отладку таких проблем, поэтому их использование внутри функции генератора в будущей версии Python меняется.

Примечание: вашей реализации требуется метод iterator.__iter__() , который просто возвращает self .


Если вы хотите перезагружаемый обернутый генератор, один из возможных вариантов - хранить его данные в другом месте:

 class Gen: def __init__(self): self.items = [] def restart(self): def ret_val(): for x in self.items: yield x return return ret_val g = Gen() for x in g.restart()(): pass for x in g.restart()(): pass