Подтвердить что ты не робот

Как выход улавливает исключение StopIteration?

Почему в примере функция завершается:

def func(iterable):
    while True:
        val = next(iterable)
        yield val

но если я снимаю функцию оператора yield, вы вызовете исключение StopIteration?

РЕДАКТИРОВАТЬ: Извините за то, что вы обманываете вас, ребята. Я знаю, что такое генераторы и как их использовать. Конечно, когда я сказал, что функция прекращается, я не имел в виду нетерпеливую оценку функции. Я просто подразумевал, что когда я использую функцию для генерации генератора:

gen = func(iterable)

в случае func он работает и возвращает тот же самый генератор, но в случае func2:

def func2(iterable):
    while True:
        val = next(iterable)

он вызывает StopIteration вместо None return или бесконечного цикла.

Позвольте мне быть более конкретным. В itertools есть функция tee, которая эквивалентна:

def tee(iterable, n=2):
    it = iter(iterable)
    deques = [collections.deque() for i in range(n)]
    def gen(mydeque):
        while True:
            if not mydeque:             # when the local deque is empty
                newval = next(it)       # fetch a new value and
                for d in deques:        # load it to all the deques
                    d.append(newval)
            yield mydeque.popleft()
    return tuple(gen(d) for d in deques)

Существует, по сути, некоторая магия, потому что вложенная функция gen имеет бесконечный цикл без операторов break. Функция gen прекращается из-за исключения StopIteration, когда в нем нет элементов. Но он заканчивается корректно (без привлечения исключений), т.е. Просто останавливает цикл. Итак, вопрос: где обрабатывается StopIteration?

4b9b3361

Ответ 1

Чтобы ответить на вопрос о том, где StopIteration попадает в генератор gen, созданный внутри itertools.tee: это не так. До потребителя результатов tee можно получить исключение по мере их повторения.

Прежде всего, важно отметить, что функция генератора (любая функция с выражением yield в ней в любом месте) принципиально отличается от нормальной. Вместо запуска кода функции, когда он вызывается, вместо этого вы просто получаете объект generator при вызове функции. Только когда вы будете перебирать генератор, вы запустите код.

Функция генератора никогда не завершит итерацию без повышения StopIteration (если только вместо этого не возникает другое исключение). StopIteration - это сигнал от генератора, что он сделан, и он не является обязательным. Если вы достигнете инструкции return или конца кода функции генератора, не поднимая ничего, Python поднимет StopIteration для вас!

Это отличается от обычных функций, которые возвращают None, если они достигают конца, не возвращая ничего. Он связан с различными способами работы генераторов, как я описал выше.

Здесь приведен пример функции-генератора, которая упростит процесс повышения StopIteration:

def simple_generator():
    yield "foo"
    yield "bar"
    # StopIteration will be raised here automatically

Вот что происходит, когда вы его потребляете:

>>> g = simple_generator()
>>> next(g)
'foo'
>>> next(g)
'bar'
>>> next(g)
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    next(g)
StopIteration

Вызов simple_generator всегда возвращает объект generator немедленно (без запуска какого-либо кода в функции). Каждый вызов next объекта-генератора запускает код до следующего оператора yield и возвращает полученное значение. Если больше нет, то StopIteration будет поднят.

Теперь вы обычно не видите исключений StopIteration. Причина этого заключается в том, что вы обычно потребляете генераторы внутри циклов for. Оператор for будет автоматически вызывать next снова и снова до тех пор, пока StopIteration не будет поднят. Он поймает и подавит исключение StopIteration для вас, поэтому вам не нужно возиться с блоками try/except, чтобы справиться с ним.

A for цикл, подобный for item in iterable: do_suff(item), почти точно эквивалентен этому циклу while (единственное отличие состоит в том, что реальному for не нужна временная переменная для хранения итератора):

iterator = iter(iterable)
try:
    while True:
        item = next(iterator)
        do_stuff(item)
except StopIteration:
    pass
finally:
    del iterator

Функция генератора gen, показанная выше, является одним из исключений. Он использует исключение StopIteration, созданное итератором, которое оно потребляет, поскольку оно является собственным сигналом о том, что оно выполняется при повторении. То есть, вместо того, чтобы ловить StopIteration, а затем вырваться из цикла, он просто позволяет исключить возможность неотображения (предположительно, чтобы быть пойманным некоторым кодом более высокого уровня).

Не связанный с основным вопросом, есть еще одна вещь, которую я хочу указать. В коде вы вызываете next для переменной с именем iterable. Если вы берете это имя в качестве документации для того, какой тип объекта вы получите, это не обязательно безопасно.

next является частью протокола iterator, а не протокола iterable (или container). Он может работать для некоторых типов итераций (таких как файлы и генераторы, поскольку эти типы являются их собственными итераторами), но он будет терпеть неудачу для других итераций, таких как кортежи и списки. Более правильным подходом является вызов iter по вашему значению iterable, а затем вызов next на итераторе, который вы получаете. (Или просто используйте циклы for, которые вызывают как iter, так и next для вас в подходящее время!)

Изменить: я просто нашел свой собственный ответ в поиске Google по соответствующему вопросу, и я подумал, что обновляю, чтобы указать, что ответ выше не будет полностью правдой в будущих версиях Python. PEP 479 делает его ошибкой, позволяя StopIteration выскочить из функции генератора. Если это произойдет, Python превратит его в исключение RuntimeError.

Это означает, что код, подобный примерам в itertools, которые используют StopIteration для выхода из функции генератора, необходимо будет изменить. Обычно вам нужно поймать исключение с помощью try/except, а затем сделать return.

Потому что это несовместимое изменение в обратном направлении, постепенное его постепенное. В Python 3.5 весь код будет работать по-прежнему по умолчанию, но вы можете получить новое поведение с помощью from __future__ import generator_stop. В Python 3.6 код все равно будет работать, но он даст предупреждение. В Python 3.7 новое поведение будет применяться все время.

Ответ 2

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

С учетом вашей функции и итерации:

def func(iterable):
    while True:
        val = next(iterable)
        yield val

iterable = iter([1, 2, 3])

Это неправильный способ назвать это:

func(iterable)

Это правильный путь:

for item in func(iterable):
    # do something with item

Вы также можете сохранить генератор в переменной и вызвать next() на нем (или перебрать его каким-либо другим способом):

gen = func(iterable)
print(next(gen))   # prints 1
print(next(gen))   # prints 2
print(next(gen))   # prints 3
print(next(gen))   # StopIteration

Кстати, лучший способ написать вашу функцию выглядит следующим образом:

def func(iterable):
    for item in iterable:
        yield item

Или в Python 3.3 и более поздних версиях:

def func(iterable):
    yield from iter(iterable)
Конечно, реальные генераторы редко настолько тривиальны.: -)

Ответ 3

Без yield вы перебираете весь iterable, не останавливаясь, чтобы что-либо сделать с помощью val. Цикл while не захватывает исключение StopIteration. Эквивалентный цикл for:

def func(iterable):
    for val in iterable:
        pass

который захватывает StopIteration и просто выходит из цикла и, таким образом, возвращается из функции.

Вы можете явно исключить исключение:

def func(iterable):
    while True:
        try:
            val = next(iterable)
        except StopIteration:
            break

Ответ 4

yield не улавливает StopIteration. Что yield делает для вашей функции, это приводит к тому, что она становится функцией генератора, а не обычной функцией. Таким образом, объект, возвращаемый из вызова функции, является итерируемым объектом (который вычисляет следующее значение, когда вы запрашиваете его с помощью функции next (которая неявно называется вызываемым циклом for)). Если вы оставите оператор yield из него, тогда python выполнит весь цикл while сразу, который заканчивает исчерпывание итерации (если он конечен) и поднимает StopIteration вправо, когда вы его вызываете.

рассмотреть следующие вопросы:

x = func(x for x in [])
next(x)  #raises StopIteration

A for цикл получает исключение. То, как он знает, когда прекратить вызов next в итерабельном, который вы ему дали.