Возвращение в генератор вместе с выходом в 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]