Возвращение в генератор вместе с выходом в Python 3.3
В Python 2 произошла ошибка, когда возврат был вместе с выходом в определении функции. Но для этого кода в Python 3.3
def f():
return 3
yield 2
x = f()
print(x.__next__())
нет ошибки, возвращаемой в функции с доходностью. Однако, когда вызывается функция __next__
, тогда возникает исключение StopIteration. Почему не возвращается только значение 3
? Это как-то проигнорировано?
Ответы
Ответ 1
Это новая функция в Python 3.3 (как отмечает комментарий, она даже не работает в 3.2). Подобно тому, как return
в генераторе уже давно эквивалентен raise StopIteration()
, return <something>
в генераторе теперь эквивалентен raise StopIteration(<something>)
. По этой причине исключение, которое вы видите, должно быть напечатано как StopIteration: 3
, и значение доступно через атрибут value
объекта исключения. Если генератору делегировано использование синтаксиса (также нового) yield from
, это результат. Подробнее см. PEP 380.
def f():
return 1
yield 2
def g():
x = yield from f()
print(x)
# g is still a generator so we need to iterate to run it:
for _ in g():
pass
Отпечатает 1
, но не 2
.
Ответ 2
Возвращаемое значение не игнорируется, но генераторы дают только значения, a return
просто заканчивает генератор, в этом случае раньше. В этом случае продвижение генератора никогда не достигает инструкции yield
.
Всякий раз, когда итератор достигает "конца" значений, чтобы получить, a StopIteration
должен быть поднят. Генераторы не являются исключением. Начиная с Python 3.3, любое выражение return
становится значением исключения:
>>> def gen():
... return 3
... yield 2
...
>>> try:
... next(gen())
... except StopIteration as ex:
... e = ex
...
>>> e
StopIteration(3,)
>>> e.value
3
Используйте функцию next()
для перемещения итераторов вместо прямого вызова .__next__()
:
print(next(x))
Ответ 3
Этот ответ совершенно не связан с вопросом, но может пригодиться людям, попавшим сюда после веб-поиска.
Вот небольшая вспомогательная функция, которая превратит любое возможное возвращаемое значение в полученное значение:
def generator():
yield 1
yield 2
return 3
def yield_all(gen):
while True:
try:
yield next(gen)
except StopIteration as e:
yield e.value
break
print([i for i in yield_all(generator())])
[1, 2, 3]
Или как декоратор:
import functools
def yield_all(func):
gen = func()
@functools.wraps(func)
def wrapper():
while True:
try:
yield next(gen)
except StopIteration as e:
yield e.value
break
return wrapper
@yield_all
def a():
yield 1
yield 2
return 3
print([i for i in a()])
[1, 2, 3]