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().