2016-05-03 4 views
4

Я экспериментировал с генераторами в Python 3 и написал это довольно надуманный генератор:Python: Поведение Send() в генераторах

def send_gen(): 
    print(" send_gen(): will yield 1") 
    x = yield 1 
    print(" send_gen(): sent in '{}'".format(x)) 
    # yield # causes StopIteration when left out 


gen = send_gen() 
print("yielded {}".format(gen.__next__())) 

print("running gen.send()") 
gen.send("a string") 

Выход:

send_gen(): will yield 1 
yielded 1 
running gen.send() 
    send_gen(): sent in 'a string' 
Traceback (most recent call last): 
    File "gen_test.py", line 12, in <module> 
    gen.send("a string") 
StopIteration 

Так gen.__next__() достигает линии x = yield 1 и дает 1. Я думал, что x будет присвоено None, затем gen.send() будет искать следующийyield заявление, потому что x = yield 1 "используется", затем получить StopIteration.

Вместо того, что кажется, произошло то, что x отправляется «строка», напечатанный, затем затем попытки питона, чтобы искать следующий yield и получает StopIteration.

Так я попробовать это:

def send_gen(): 
    x = yield 1 
    print(" send_gen(): sent in '{}'".format(x)) 


gen = send_gen() 
print("yielded : {}".format(gen.send(None))) 

Выход:

yielded : 1 

Но теперь нет никакой ошибки. send() похоже не пытался найти следующийyield заявление после присвоения x по None.

Почему поведение немного отличается? Это связано с тем, как я начал генераторы?

ответ

4

Поведение - не другое; вы никогда не продвигались выше первого выражения yield в генераторе во второй настройке. Имейте в виду, что StopIteration является не является ошибкой; это нормальное поведение, ожидаемый сигнал должен срабатывать всякий раз, когда генератор закончился. Во втором примере вы просто не дошли до конца генератора.

Когда генератор достигает выражения yield, выполнение приостанавливает прямо там, выражение не может произвести что-либо внутри генератора до тех пор, пока оно не будет возобновлено. Либо gen.__next__(), либо gen.send() оба будут возобновлять исполнение с этой точки, с выражением yield либо производя значение, принятое в gen.send(), либо None. Вы можете увидеть gen.__next__() как gen.send(None), если это поможет. Единственное, что нужно реализовать здесь, это то, что gen.send() имеет yield возвращает отправленное значение сначала и , затем генератор переходит к следующему yield.

Таким образом, учитывая ваш первый пример генератора, это происходит:

  1. gen = send_gen() создает объект генератора. Код приостанавливается в самом верху функции, ничего не выполняется.

  2. Вы либо звоните gen.__next__(), либо gen.send(None); генератор начинает и не выполняет до первого yield выражения:

    print(" send_gen(): will yield 1") 
    yield 1 
    

    и исполнение в настоящее время паузы. gen.__next__() или gen.send(None) звонки теперь возвращаются 1, значение дается yield 1. Поскольку генератор теперь приостановлен, назначение x = ... еще не может состояться! Это произойдет только тогда, когда генератор снова будет возобновлен.

  3. В своем первом примере вы вызываете , не делайте любой звонок во второй. Таким образом, для первого примера, функция генератора возобновляется в настоящее время:

    x = <return value of the yield expression> # 'a string' in this case 
    print(" send_gen(): sent in '{}'".format(x)) 
    

    и теперь функция заканчивается, так StopIteration поднимается.

Потому что вы никогда не возобновились генератора в вашем втором примере, конец генератора не будет достигнут и не StopIteration исключения не возбуждаются.

Обратите внимание, что, так как генератор начинается в верхней части функции, нет yield выражения в тот момент, чтобы вернуть то, что вы послали с gen.send() поэтому первым gen.send() значения должно быть всегда None или исключение. Лучше всего использовать явный gen.__next__() (или, скорее, вызов функции next(gen)) для «простого» генератора, поэтому он будет приостановлен в первом выражении yield.

+0

Ahhh Я вижу. Объяснения 'x = ...' очень помогли. Благодаря ! – peonicles

3

Решающее различие в том, что вы попали в генератор в первом примере дважды, но вы попали в генератор в вашем втором примере только раз.

Когда вы определяете coroutine, то есть генератор, на который вы собираетесь отправлять аргументы, вам придется заранее «переманить» его, перейдя на первый оператор yield. Только тогда вы можете отправить значения. В первом примере вы сделали это явно, вызвав gen.__next__(), прежде чем пытаться выполнить send.

Во втором примере, вы также загрунтовать его, делая gen.send(None) (обратите внимание, что отправка в None фактически эквивалентно вызову gen.__next__() или next(gen)). Но тогда вы не пытались отправить значение во второй раз, поэтому в этом случае не было StopIteration. Генератор просто сидит там, останавливаясь на выводе, ожидая, когда вы снова ударите его, и именно поэтому вы еще не увидели печать позже.

Еще следует отметить, что если бы вы послали ничего другого, кроме None в вашем втором примере, было бы ошибкой:

TypeError: can't send non-None value to a just-started generator 

Это то, что я говорил о с «стягивания» сопрограммная.

Смежные вопросы