Что делает доходность внутри доходности?
Рассмотрим следующий код:
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)
прежде чем исчерпать генератор.