Что делает доходность внутри доходности?

Рассмотрим следующий код:

def mygen():
     yield (yield 1)
a = mygen()
print(next(a))
print(next(a)) 

Выход дает:

1
None

Что точно делает переводчик на "внешнем"?

Ответы

Ответ 1

a является объектом-генератором. При первом вызове next для него тело вычисляется с точностью до первого выражения yield (то есть первого, которое будет оценено: внутреннего). Эта yield производит значение 1 для next чтобы возвратиться, затем блокирует до следующего входа в генератор. Это производится вторым вызовом next, который не отправляет никакого значения в генератор. В результате первый (внутренний) yield оценивается как None. Это значение используется в качестве аргумента для внешнего yield, который становится возвращаемым значением второго вызова next. Если вы будете звонить в next в третий раз, вы получите исключение StopIteration.

Сравните использование метода send (вместо next), чтобы изменить значение возврата первого выражения yield.

>>> a = mygen()
>>> next(a)
1
>>> a.send(3)  # instead of next(a)
3
>>> next(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Более явный способ написания генератора был бы

def mygen():
    x = yield 1
    yield x

a = mygen()
print(a.send(None))  # outputs 1, from yield 1
print(a.send(5))     # makes yield 1 == 5, then gets 5 back from yield x
print(a.send(3))     # Raises StopIteration, as there nothing after yield x

До Python 2.5 оператор yield предоставлял одностороннюю связь между вызывающим и генератором; вызов next будет выполнять генератор до следующего оператора yield, а значение, предоставляемое ключевым словом yield будет служить возвращаемым значением next. Генератор также приостановить в точке yield заявления, ожидая следующий вызов next возобновления.

В Python 2.5 оператор yield был заменен * на выражение yield, и генераторы получили метод send. send работает очень похоже на next, но может принимать аргумент. (В остальном, предположим, что next(a) эквивалентно a.send(None).) Генератор начинает выполнение после вызова send(None), после чего он выполняется до первого yield, который возвращает значение, как и раньше. Однако теперь выражение блокируется до следующего вызова для send, после чего выражение yield оценивается как аргумент, переданный для send. Генератор теперь может получить значение при возобновлении.


* Не совсем заменены; Ответ Кодзиро более детально описывает тонкую разницу между выражением yield и выражением yield.

Ответ 2

yield имеет две формы, выражения и утверждения. Они в основном одинаковые, но я чаще всего вижу их в форме statement, где результат не будет использоваться.

def f():
    yield a thing

Но в форме выражения yield имеет значение:

def f():
    y = yield a thing

В своем вопросе вы используете обе формы:

def f():
    yield ( # statement
        yield 1 # expression
    )

Когда вы перебираете полученный генератор, вы сначала получаете результат внутреннего выражения yield

>>> x=f()
>>> next(x)
1

На этом этапе внутреннее выражение также создало значение, которое может использовать внешний оператор

>>> next(x)
>>>  # None

и теперь вы исчерпали генератор

>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Чтобы лучше понять операторы и выражения, есть хорошие ответы на другие вопросы, связанные со стековым потоком: в чем разница между выражением и оператором в Python?

Ответ 3

>>> def mygen():
...     yield (yield 1)
...
>>> a = mygen()
>>>
>>> a.send(None)
1
>>> a.send(5)
5
>>> a.send(2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>
>>>
>>>
>>> def mygen():
...     yield 1
...
>>> def mygen2():
...     yield (yield 1)
...
>>> def mygen3():
...     yield (yield (yield 1))
...
>>> a = mygen()
>>> a2 = mygen2()
>>> a3 = mygen3()
>>>
>>> a.send(None)
1
>>> a.send(0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> a2.send(None)
1
>>> a2.send(0)
0
>>> a2.send(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> a3.send(None)
1
>>> a3.send(0)
0
>>> a3.send(1)
1
>>> a3.send(2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

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


>>> def mygen():
...     print('Wait for first input')
...     x = yield # this is what we get from send
...     print(x, 'is received')
...
>>> a = mygen()
>>> a.send(None)
Wait for first input
>>> a.send('bla')
bla is received
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

yield дает следующее значение, когда вы продолжаете, если вы его получаете, и если оно не используется для предоставления следующего значения, оно используется для получения следующего

>>> def mygen():
...     print('Wait for first input')
...     x = yield # this is what we get from send
...     yield x*2 # this is what we give
...
>>> a = mygen()
>>> a.send(None)
Wait for first input
>>> a.send(5)
10
>>>

Ответ 4

Любой генератор истощает элементы, пока не исчерпает их.
В двухуровневом вложенном примере, как показано ниже, первый next дает нам элемент из самой внутренней доходности, который равен 1, следующий выход просто возвращает None, так как у него нет элементов для возврата, если вы вызовете next снова, он вернет StopIteration

def mygen():
     yield (yield 1)
a = mygen()
print(next(a))
print(next(a))
print(next(a))

Вы можете расширить этот случай, чтобы включить больше вложенных выходов, и вы увидите, что после StopIteration n next StopIteration, ниже приведен пример с 5 вложенными доходностями

def mygen():
     yield ( yield ( yield ( yield (yield 1))))
a = mygen()
print(next(a))
print(next(a))
print(next(a))
print(next(a))
print(next(a))
print(next(a))

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

Ответ 5

Возвращает объект генератора. Для подтверждения просто type(a) прежде чем исчерпать генератор.