Python: присвоение урожая и доходности
Как работает этот код, включая назначение и оператор yield? Результаты довольно сбивают.
def test1(x):
for i in x:
_ = yield i
yield _
def test2(x):
for i in x:
_ = yield i
r1 = test1([1,2,3])
r2 = test2([1,2,3])
print list(r1)
print list(r2)
Вывод:
[1, None, 2, None, 3, None]
[1, 2, 3]
Ответы
Ответ 1
Синтаксис присваивания (выражение "yield" ) позволяет обрабатывать генератор как рудиментарный сопрограмм.
Сначала предложено в PEP 342 и описано здесь: https://docs.python.org/2/reference/expressions.html#yield-expressions
Клиентский код, который работает с генератором, может передавать данные обратно в генератор, используя его метод send()
. Эти данные доступны через синтаксис присваивания.
send()
также будет итерации - поэтому он фактически включает вызов next()
.
Используя ваш пример, это то, что было бы похоже на функциональность кутутина:
>>> def test1(x):
... for i in x:
... _ = yield i
... yield _
...
>>> l = [1,2,3]
>>> gen_instance = test1(l)
>>> #First send has to be a None
>>> print gen_instance.send(None)
1
>>> print gen_instance.send("A")
A
>>> print gen_instance.send("B")
2
>>> print gen_instance.send("C")
C
>>> print gen_instance.send("D")
3
>>> print gen_instance.send("E")
E
>>> print gen_instance.send("F")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Обратите внимание, что некоторые из отправлений теряются из-за второго yield
в каждой итерации цикла, которая не фиксирует отправленные данные.
EDIT:
Забыл объяснить None
, приведенный в вашем примере.
Из https://docs.python.org/2/reference/expressions.html#generator.next:
Когда функция генератора возобновляется с помощью метода next(), текущий yield всегда оценивает значение None.
next()
используется при использовании синтаксиса итерации.
Ответ 2
Чтобы расширить ответ TigerhawkT3, причина, по которой операция yield возвращает None
в вашем коде, состоит в том, что list(r1)
не отправляет ничего в генератор. Попробуйте следующее:
def test1(x):
for i in x:
_ = yield i
yield _
r1 = test1([1, 2, 3])
for x in r1:
print(' x', x)
print('send', r1.send('hello!'))
Вывод:
x 1
send hello!
x 2
send hello!
x 3
send hello!
Здесь может быть полезен несколько подготовленный пример, в котором можно было бы отправлять значения в генератор:
def changeable_count(start=0):
current = start
while True:
changed_current = yield current
if changed_current:
current = changed_current
else:
current += 1
counter = changeable_count(10)
for x in range(20):
print(next(counter), end=' ')
print()
print()
print('Sending 51, printing return value:', counter.send(51))
print()
for x in range(20):
print(next(counter), end=' ')
print()
print()
print('Sending 42, NOT printing return value')
print()
counter.send(42)
for x in range(20):
print(next(counter), end=' ')
print()
Вывод:
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
Sending 51, printing return value: 51
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
Sending 42, NOT printing return value
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
Ответ 3
_ = yield i
yield _
Сначала это yield
значение, на которое ссылается i
, например. 1
. Затем он возвращает значение, возвращаемое операцией yield
, которая равна None
. Он делает это на каждой итерации цикла.
for i in x:
_ = yield i
Это просто yield
значение, на которое ссылается i
, например. 1
, затем переходим к следующей итерации цикла, создавая 2
, затем 3
.
В отличие от return
ключевое слово yield
может использоваться в выражении:
x = return 0 # SyntaxError
x = yield 0 # perfectly fine
Теперь, когда интерпретатор увидит a yield
, он будет генерировать указанное значение. Однако, когда он это делает, эта операция возвращает значение None
, так же как mylist.append(0)
или print('hello')
будет return
значение None
. Когда вы присваиваете этот результат ссылке, например _
, вы сохраняете это значение None
.
Итак, в первом фрагменте вы получаете объект, затем вы сохраняете "результат" этой операции yield
, которая None
, а затем вы yield
, что None
. Во втором фрагменте вы даете объект, затем вы сохраняете "результат" этой операции yield
, но вы никогда не yield
, поэтому результат None
не появляется на выходе.
Обратите внимание, что yield
не всегда возвращает None
- это именно то, что вы отправили генератору с помощью send()
. Поскольку в этом случае ничего не было, вы получаете None
. См. этот ответ для получения дополнительной информации о send()
.