Что делает доходность внутри доходности? - PullRequest
55 голосов
/ 30 апреля 2019

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

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

Выходные данные:

1
None

Что точно делает переводчик на «внешнем» выходе?

Ответы [ 4 ]

48 голосов
/ 30 апреля 2019

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's 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.

25 голосов
/ 30 апреля 2019

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 голосов
/ 30 апреля 2019
>>> 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
>>>
1 голос
/ 30 апреля 2019

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

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

Вы можете расширить этот случай, включив в него больше вложенных выходов, и вы увидите, что после вызова 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))

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

...