Чтобы ответить на ваш вопрос о том, где 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
, чтобы справиться с ним.
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
для вас в нужное время!)
Edit: Я только что нашел мой собственный ответ в поиске 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 новое поведение будет применяться все время.
Как вы это называете? –