Назначение функции отправки генератора?

165

Может кто-нибудь привести пример того, почему существует функция «send», связанная с функцией генератора Python? Я полностью понимаю функцию доходности. Однако, функция отправки сбивает меня с толку. Документация по этому методу свернута:

generator.send(value)

Возобновляет выполнение и «отправляет» значение в функцию генератора. Аргумент value становится результатом текущего выражения yield. Метод send () возвращает следующее значение, полученное генератором, или вызывает StopItered, если генератор завершает работу без получения другого значения.

Что это значит? Я думал, что значение было входом в функцию? Фраза «метод send () возвращает следующее значение, полученное генератором», по-видимому, также является точной целью функции yield; yield возвращает следующее значение, полученное генератором ...

Может кто-нибудь дать мне пример генератора, использующего send, который выполняет то, что yield не может?

Томми
источник
3
дубликат: stackoverflow.com/questions/12637768/…
Бас Суинкелс,
3
Добавлен еще один пример из реальной жизни (чтение с FTP), когда обратные вызовы превращаются в генератор, используемый изнутри
Ян Влчинский,
2
Стоит отметить, что «Когда send()вызывается для запуска генератора, он должен вызываться Noneв качестве аргумента, потому что нет выражения yield, которое могло бы получить значение.», Цитируется в официальном документе и для которого приведена цитата в вопросе отсутствует.
Рик

Ответы:

147

Он используется для отправки значений в генератор, который только что дал. Вот искусственный (бесполезный) пояснительный пример:

>>> def double_inputs():
...     while True:
...         x = yield
...         yield x * 2
...
>>> gen = double_inputs()
>>> next(gen)       # run up to the first yield
>>> gen.send(10)    # goes into 'x' variable
20
>>> next(gen)       # run up to the next yield
>>> gen.send(6)     # goes into 'x' again
12
>>> next(gen)       # run up to the next yield
>>> gen.send(94.3)  # goes into 'x' again
188.5999999999999

Вы не можете сделать это только с yield.

Что касается того, почему это полезно, один из лучших вариантов использования, которые я видел, это Twisted @defer.inlineCallbacks. По сути, это позволяет вам написать такую ​​функцию:

@defer.inlineCallbacks
def doStuff():
    result = yield takesTwoSeconds()
    nextResult = yield takesTenSeconds(result * 10)
    defer.returnValue(nextResult / 10)

То, что происходит, - это то, что takesTwoSeconds()возвращает значение Deferred, которое обещает, что значение будет вычислено позже. Twisted может запустить вычисления в другом потоке. Когда вычисление выполнено, оно передает его в отложенное состояние, а затем значение возвращается в doStuff()функцию. Таким образом, он doStuff()может выглядеть более или менее похожим на обычную процедурную функцию, за исключением того, что он может выполнять все виды вычислений, обратных вызовов и т. Д. Альтернативой этой функции было бы сделать что-то вроде:

def doStuff():
    returnDeferred = defer.Deferred()
    def gotNextResult(nextResult):
        returnDeferred.callback(nextResult / 10)
    def gotResult(result):
        takesTenSeconds(result * 10).addCallback(gotNextResult)
    takesTwoSeconds().addCallback(gotResult)
    return returnDeferred

Это намного более запутанный и громоздкий.

Клаудиу
источник
2
Можете ли вы объяснить, какова цель этого? Почему это нельзя воссоздать с помощью double_inputs (начальный номер) и yield?
Томми
@Tommy: о, потому что полученные значения не имеют ничего общего с предыдущим. позвольте мне изменить пример
Клавдиу
зачем вам использовать это тогда над простой функцией, которая удваивает ввод?
Томми
4
@ Томми: Вы бы не. Первый пример - просто объяснить, что он делает. Второй пример для действительно полезного варианта использования.
Клавдиу
1
@ Tommy: я бы сказал, если вы действительно хотите знать, проверить эту презентацию и проработать все это. Короткого ответа будет недостаточно, потому что тогда вы просто скажете: «Но разве я не могу сделать это так?» и т. д.
Клавдиу
96

Эта функция для написания сопрограмм

def coroutine():
    for i in range(1, 10):
        print("From generator {}".format((yield i)))
c = coroutine()
c.send(None)
try:
    while True:
        print("From user {}".format(c.send(1)))
except StopIteration: pass

печать

From generator 1
From user 2
From generator 1
From user 3
From generator 1
From user 4
...

Видите, как контроль передается взад и вперед? Это сопрограммы. Они могут быть использованы для всех видов интересных вещей, таких как асинхронный ввод-вывод и тому подобное.

Думайте об этом так, с генератором и без отправки, это улица с односторонним движением

==========       yield      ========
Generator |   ------------> | User |
==========                  ========

Но с отправкой это становится улицей с двусторонним движением

==========       yield       ========
Generator |   ------------>  | User |
==========    <------------  ========
                  send

Это открывает двери для пользователя, настраивая поведение генераторов на лету, и генератор отвечает пользователю.

Даниэль Гратцер
источник
3
но функция генератора может принимать параметры. Как «Отправить» выходит за рамки отправки параметра в генератор?
Томми
13
@ Tommy, потому что вы не можете изменить параметры генератора во время его работы. Вы даете ему параметры, он работает, готово. С помощью send вы даете ему параметры, он запускается некоторое время, вы отправляете ему значение и он делает что-то другое, повторяйте
Даниэль Гратцер
2
@ Tommy Это перезапустит генератор, который заставит вас переделать большую работу
Даниэль Гратцер
5
Не могли бы вы объяснить цель отправки None до всего?
Шубхам Аггарвал
2
@ShubhamAggarwal Это сделано, чтобы «запустить» генератор. Это просто то, что нужно сделать. Это имеет некоторый смысл, когда вы думаете об этом, так как при первом вызове send()генератор еще не достиг ключевого слова yield.
Майкл
50

Это может кому-то помочь. Вот генератор, который не зависит от функции отправки. Он принимает параметр number при создании экземпляра и не зависит от send:

>>> def double_number(number):
...     while True:
...         number *=2 
...         yield number
... 
>>> c = double_number(4)
>>> c.send(None)
8
>>> c.next()
16
>>> c.next()
32
>>> c.send(8)
64
>>> c.send(8)
128
>>> c.send(8)
256

Теперь вот как вы выполняете функцию того же типа, используя send, поэтому на каждой итерации вы можете изменять значение number:

def double_number(number):
    while True:
        number *= 2
        number = yield number

Вот как это выглядит, поскольку вы можете видеть, что отправка нового значения для числа меняет результат:

>>> def double_number(number):
...     while True:
...         number *= 2
...         number = yield number
...
>>> c = double_number(4)
>>> 
>>> c.send(None)
8
>>> c.send(5) #10
10
>>> c.send(1500) #3000
3000
>>> c.send(3) #6
6

Вы также можете поместить это в цикл for как таковой:

for x in range(10):
    n = c.send(n)
    print n

Для получения дополнительной помощи ознакомьтесь с этим прекрасным учебником .

radtek
источник
12
Это сравнение между функцией, на которую send () не влияет, с функцией, которая действительно помогает. Спасибо!
Манас Бахадж
Как это может быть иллюстративным примером цели send? Простое lambda x: x * 2делает то же самое в гораздо менее запутанном виде.
user209974
Это использует отправить? Иди и добавь свой ответ.
Радтек
17

Некоторые варианты использования генератора и send()

Генераторы с send()разрешением:

  • запоминание внутреннего состояния исполнения
    • на каком этапе мы находимся
    • каков текущий статус наших данных
  • возвращающая последовательность значений
  • получение последовательности входов

Вот несколько вариантов использования:

Наблюдаю за попыткой следовать рецепту

Давайте получим рецепт, который ожидает предопределенный набор входов в некотором порядке.

Мы можем:

  • создать watched_attemptэкземпляр по рецепту
  • пусть он получит некоторые входные данные
  • с каждым входом возвращают информацию о том, что в данный момент находится в банке
  • при каждом входном контроле проверяется, что вход является ожидаемым (и потерпит неудачу, если это не так)

    def recipe():
        pot = []
        action = yield pot
        assert action == ("add", "water")
        pot.append(action[1])
    
        action = yield pot
        assert action == ("add", "salt")
        pot.append(action[1])
    
        action = yield pot
        assert action == ("boil", "water")
    
        action = yield pot
        assert action == ("add", "pasta")
        pot.append(action[1])
    
        action = yield pot
        assert action == ("decant", "water")
        pot.remove("water")
    
        action = yield pot
        assert action == ("serve")
        pot = []
        yield pot

Чтобы использовать его, сначала создайте watched_attemptэкземпляр:

>>> watched_attempt = recipe()                                                                         
>>> watched_attempt.next()                                                                                     
[]                                                                                                     

Призыв к .next()необходимо запустить выполнение генератора.

Возвращаемое значение показывает, что наш банк в настоящее время пуст.

Теперь выполните несколько действий, следуя тому, что ожидает рецепт:

>>> watched_attempt.send(("add", "water"))                                                                     
['water']                                                                                              
>>> watched_attempt.send(("add", "salt"))                                                                      
['water', 'salt']                                                                                      
>>> watched_attempt.send(("boil", "water"))                                                                    
['water', 'salt']                                                                                      
>>> watched_attempt.send(("add", "pasta"))                                                                     
['water', 'salt', 'pasta']                                                                             
>>> watched_attempt.send(("decant", "water"))                                                                  
['salt', 'pasta']                                                                                      
>>> watched_attempt.send(("serve"))                                                                            
[] 

Как видим, горшок, наконец, пуст.

В случае, если кто-то не будет следовать рецепту, он потерпит неудачу (что может быть желательным результатом наблюдаемой попытки что-то приготовить - просто учась, мы не уделяем достаточно внимания при получении инструкций.

>>> watched_attempt = running.recipe()                                                                         
>>> watched_attempt.next()                                                                                     
[]                                                                                                     
>>> watched_attempt.send(("add", "water"))                                                                     
['water']                                                                                              
>>> watched_attempt.send(("add", "pasta")) 

---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-21-facdf014fe8e> in <module>()
----> 1 watched_attempt.send(("add", "pasta"))

/home/javl/sandbox/stack/send/running.py in recipe()
     29
     30     action = yield pot
---> 31     assert action == ("add", "salt")
     32     pot.append(action[1])
     33

AssertionError:

Заметь:

  • существует линейная последовательность ожидаемых шагов
  • шаги могут отличаться (некоторые удаляют, некоторые добавляют в банк)
  • нам удается делать все это с помощью функции / генератора - нет необходимости использовать сложный класс или подобные структуры.

Промежуточные итоги

Мы можем использовать генератор для отслеживания итогового значения отправленных ему значений.

Каждый раз, когда мы добавляем число, возвращается количество входов и общая сумма (действительна на момент, когда в него был отправлен предыдущий вход).

from collections import namedtuple

RunningTotal = namedtuple("RunningTotal", ["n", "total"])


def runningtotals(n=0, total=0):
    while True:
        delta = yield RunningTotal(n, total)
        if delta:
            n += 1
            total += delta


if __name__ == "__main__":
    nums = [9, 8, None, 3, 4, 2, 1]

    bookeeper = runningtotals()
    print bookeeper.next()
    for num in nums:
        print num, bookeeper.send(num)

Вывод будет выглядеть так:

RunningTotal(n=0, total=0)
9 RunningTotal(n=1, total=9)
8 RunningTotal(n=2, total=17)
None RunningTotal(n=2, total=17)
3 RunningTotal(n=3, total=20)
4 RunningTotal(n=4, total=24)
2 RunningTotal(n=5, total=26)
1 RunningTotal(n=6, total=27)
Ян Влчинский
источник
3
Я запускаю ваш пример и в Python 3 кажется, что watched_attempt.next () должен быть заменен следующим (watched_attempt).
thanos.a
15

В send()контролируете метода , что значение слева от выражения текучести будет.

Чтобы понять, как yield отличается и какое значение он имеет, давайте сначала быстро обновим код заказа Python.

Раздел 6.15 Порядок оценки

Python оценивает выражения слева направо. Обратите внимание, что при оценке присваивания правая сторона оценивается перед левой стороной.

Таким образом, выражение a = bв правой части вычисляется первым.

Как показывает следующее , что a[p('left')] = p('right')правая рука оценивается первым.

>>> def p(side):
...     print(side)
...     return 0
... 
>>> a[p('left')] = p('right')
right
left
>>> 
>>> 
>>> [p('left'), p('right')]
left
right
[0, 0]

Что делает yield ?, yield, приостанавливает выполнение функции и возвращает вызывающей стороне и возобновляет выполнение с того же места, на котором остановился перед приостановкой.

Где именно приостановлено исполнение? Возможно, вы уже догадались ... выполнение приостановлено между правой и левой сторонами выражения yield. Таким new_val = yield old_valобразом, выполнение останавливается на =знаке, и значение справа (которое находится перед приостановкой, а также является значением, возвращаемым вызывающей стороне) может отличаться от значения слева (которое является значением, назначаемым после возобновления). исполнение).

yield дает 2 значения, одно справа и другое слева.

Как вы контролируете значение в левой части выражения yield? через .send()метод.

6.2.9. Выражения доходности

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

user2059857
источник
13

В sendметоде реализует сопрограмму .

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

Йохен Ритцель
источник
6

Слово «урожайность» имеет два значения: производить что-то (например, приносить кукурузу) и останавливать, чтобы позволить кому-то / чему-либо еще продолжать движение (например, автомобили, уступающие пешеходам). Оба определения применяются к yieldключевому слову Python ; что делает функции генератора особенными, так это то, что в отличие от обычных функций, значения могут быть «возвращены» вызывающей стороне, просто останавливая, а не завершая функцию генератора.

Проще всего представить генератор как один конец двунаправленной трубы с «левым» концом и «правым» концом; этот канал является средой, по которой значения передаются между самим генератором и телом функции генератора. Каждый конец канала имеет две операции: pushотправляет значение и блокирует, пока другой конец канала не извлекает значение и ничего не возвращает; иpull, который блокируется до тех пор, пока другой конец канала не вытолкнет значение и не вернет переданное значение. Во время выполнения выполнение отскакивает назад и вперед между контекстами по обе стороны канала - каждая сторона выполняется до тех пор, пока не отправит значение другой стороне, после чего остановится, разрешит выполнение другой стороне и ожидает значения в Возврат, после чего другая сторона останавливается, и она возобновляется. Другими словами, каждый конец канала работает с момента получения значения до момента отправки значения.

Канал является функционально-симметричным, но - по соглашению, который я определил в этом ответе - левый конец доступен только внутри тела функции генератора и доступен через yieldключевое слово, в то время как правый конец является генератором и доступен через sendфункция генератора . Как особые интерфейсы к их соответствующим концам трубы, yieldи sendвыполняют двойную обязанность: каждый из них и толкает и тянет значения к / от их концов трубы, yieldтолкая вправо и тянет влево, в то время как sendделает противоположное. Эта двойная обязанность - суть путаницы вокруг семантики таких утверждений, как x = yield y. Ломать yieldи sendвниз в два этапа явно тяни / толкай сделает их семантика гораздо более ясно:

  1. Предположим, gэто генератор. g.sendтолкает значение влево через правый конец трубы.
  2. Выполнение в контексте gпауз, позволяющих запустить тело функции генератора.
  3. Выдвигаемое значение g.sendтянется влево yieldи принимается на левом конце трубы. В x = yield y, xприсваивается значение вытягивания.
  4. Выполнение продолжается в теле функции генератора, пока не yieldбудет достигнута следующая строка, содержащая .
  5. yieldтолкает значение вправо через левый конец трубы, обратно до g.send. В x = yield y, yпроталкивается вправо через трубу.
  6. Выполнение в теле функции генератора приостанавливается, позволяя внешней области продолжить там, где она остановилась.
  7. g.send возобновляет и извлекает значение и возвращает его пользователю.
  8. При g.sendследующем вызове вернитесь к шагу 1.

Несмотря на цикличность, эта процедура имеет начало: когда g.send(None)- то, что next(g)является сокращением - сначала вызывается (нельзя передавать что-либо, кроме Noneпервого sendвызова). И это может иметь конец: когда больше нет yieldоператоров, которые должны быть достигнуты в теле функции генератора.

Вы понимаете, что делает yieldзаявление (или, точнее, генераторы) таким особенным? В отличие от returnключевого слова « трусливый» , yieldон может передавать значения своему вызывающему и получать значения от своего вызывающего без прерывания функции, в которой он живет! (Конечно, если вы хотите завершить функцию - или генератор - также удобно иметь returnключевое слово.) Когда встречается yieldоператор, функция генератора просто делает паузу, а затем возвращается обратно туда, где она оставлена выкл при отправке другого значения. И sendэто просто интерфейс для связи с внутренней функцией генератора извне.

Если мы действительно хотим сломать эту аналогию «push / pull / pipe» настолько далеко, насколько сможем, мы получим следующий псевдокод, который действительно возвращает домой, помимо шагов 1-5, yieldи sendявляется двумя сторонами одной и той же трубы монет :

  1. right_end.push(None) # the first half of g.send; sending None is what starts a generator
  2. right_end.pause()
  3. left_end.start()
  4. initial_value = left_end.pull()
  5. if initial_value is not None: raise TypeError("can't send non-None value to a just-started generator")
  6. left_end.do_stuff()
  7. left_end.push(y) # the first half of yield
  8. left_end.pause()
  9. right_end.resume()
  10. value1 = right_end.pull() # the second half of g.send
  11. right_end.do_stuff()
  12. right_end.push(value2) # the first half of g.send (again, but with a different value)
  13. right_end.pause()
  14. left_end.resume()
  15. x = left_end.pull() # the second half of yield
  16. goto 6

Ключ преобразования является то , что мы имеем раскол x = yield yи value1 = g.send(value2)каждую из двух утверждений: left_end.push(y)и x = left_end.pull(); и value1 = right_end.pull()и right_end.push(value2). Есть два особых случая yieldключевого слова: x = yieldи yield y. Это синтаксический сахар соответственно для x = yield Noneи _ = yield y # discarding value.

Конкретные детали относительно точного порядка, в котором значения отправляются через канал, см. Ниже.


Далее следует довольно длинная конкретная модель вышесказанного. Во- первых, следует в первую очередь отметить , что для любого генератора g, next(g)точно эквивалентно g.send(None). Имея это в виду, мы можем сосредоточиться только на том, как sendработает, и говорить только о продвижении генератора с send.

Предположим, у нас есть

def f(y):  # This is the "generator function" referenced above
    while True:
        x = yield y
        y = x
g = f(1)
g.send(None)  # yields 1
g.send(2)     # yields 2

Теперь определение fгрубо десугаров для следующей обычной (не генераторной) функции:

def f(y):
    bidirectional_pipe = BidirectionalPipe()
    left_end = bidirectional_pipe.left_end
    right_end = bidirectional_pipe.right_end

    def impl():
        initial_value = left_end.pull()
        if initial_value is not None:
            raise TypeError(
                "can't send non-None value to a just-started generator"
            )

        while True:
            left_end.push(y)
            x = left_end.pull()
            y = x

    def send(value):
        right_end.push(value)
        return right_end.pull()

    right_end.send = send

    # This isn't real Python; normally, returning exits the function. But
    # pretend that it's possible to return a value from a function and then
    # continue execution -- this is exactly the problem that generators were
    # designed to solve!
    return right_end
    impl()

В этой трансформации произошло следующее f:

  1. Мы переместили реализацию во вложенную функцию.
  2. Мы создали двунаправленный канал, left_endк которому будет обращаться вложенная функция и right_endкоторый будет возвращен и получен доступ из внешней области видимости - right_endэто то, что мы знаем как объект генератора.
  3. В рамках вложенной функции самое первое, что мы делаем, это проверяем, то left_end.pull()есть Noneпотребляемое значение в процессе.
  4. Внутри вложенной функции оператор x = yield yзаменен двумя строками: left_end.push(y)и x = left_end.pull().
  5. Мы определили sendфункцию для right_end, которая является аналогом двух строк, которыми мы заменили x = yield yоператор на предыдущем шаге.

В этом фэнтезийном мире, где функции могут продолжаться после возвращения, gназначается right_endи затем impl()вызывается. Итак, в нашем примере выше, если бы мы следили за выполнением строки за строкой, то примерно следующее:

left_end = bidirectional_pipe.left_end
right_end = bidirectional_pipe.right_end

y = 1  # from g = f(1)

# None pushed by first half of g.send(None)
right_end.push(None)
# The above push blocks, so the outer scope halts and lets `f` run until
# *it* blocks

# Receive the pushed value, None
initial_value = left_end.pull()

if initial_value is not None:  # ok, `g` sent None
    raise TypeError(
        "can't send non-None value to a just-started generator"
    )

left_end.push(y)
# The above line blocks, so `f` pauses and g.send picks up where it left off

# y, aka 1, is pulled by right_end and returned by `g.send(None)`
right_end.pull()

# Rinse and repeat
# 2 pushed by first half of g.send(2)
right_end.push(2)
# Once again the above blocks, so g.send (the outer scope) halts and `f` resumes

# Receive the pushed value, 2
x = left_end.pull()
y = x  # y == x == 2

left_end.push(y)
# The above line blocks, so `f` pauses and g.send(2) picks up where it left off

# y, aka 2, is pulled by right_end and returned to the outer scope
right_end.pull()

x = left_end.pull()
# blocks until the next call to g.send

Это отображается точно в 16-шаговый псевдокод выше.

Существуют и другие детали, например, как распространяются ошибки и что происходит, когда вы достигаете конца генератора (канал закрыт), но это должно прояснить, как работает основной поток управления при sendиспользовании.

Используя те же правила десагеринга, давайте рассмотрим два особых случая:

def f1(x):
    while True:
        x = yield x

def f2():  # No parameter
    while True:
        x = yield x

По большей части они desugar так же, как f, единственные различия в yieldтом, как трансформируются утверждения:

def f1(x):
    # ... set up pipe

    def impl():
        # ... check that initial sent value is None

        while True:
            left_end.push(x)
            x = left_end.pull()

    # ... set up right_end


def f2():
    # ... set up pipe

    def impl():
        # ... check that initial sent value is None

        while True:
            left_end.push(x)
            x = left_end.pull()

    # ... set up right_end

Во-первых, переданное значение f1сначала отбрасывается (уступается), а затем все значения, которые извлекаются (отправляются), возвращаются (возвращаются) назад. Во втором, xне имеет значения (пока), когда это впервые приходит push, так что возникает UnboundLocalError.

BallpointBen
источник
«Аргумент 1 в g = f (1) был захвачен и назначен y в теле f, но пока True еще не начался». Почему нет? Почему бы Python не попытаться запустить этот код, пока он не встретится, например yield?
Джош
@Josh Курсор не продвигается до первого вызова send; send(None)для перемещения курсора к первому yieldоператору требуется один вызов , и только после этого последующие sendвызовы фактически отправляют «реальное» значение yield.
BallpointBen
Спасибо - это интересно, поэтому интерпретатор знает, что функция f будет yield в какой-то момент, и, следовательно, ждать, пока она не получит sendот вызывающей стороны? С нормальной функцией cal интерпретатор просто начнет выполняться fсразу, верно? В конце концов, в Python нет никакой компиляции AOT. Вы уверены, что это так? (не подвергая сомнению то, что вы говорите, я действительно просто озадачен тем, что вы здесь написали). Где я могу прочитать больше о том, как Python знает, что ему нужно подождать, прежде чем он начнет выполнять остальную часть функции?
Джош
@ Джош Я построил эту ментальную модель, просто наблюдая за тем, как работают разные генераторы игрушек, не разбираясь во внутренностях Python. Тем не менее, тот факт, что инициал send(None)дает соответствующее значение (например, 1) без отправки Noneв генератор, предполагает, что первый вызов send- это особый случай. Это сложный интерфейс для дизайна; если вы позволите первому sendотправить произвольное значение, то порядок полученных значений и отправленных значений будет на единицу меньше по сравнению с тем, что есть в настоящее время.
BallpointBen
Спасибо BallpointBen. Очень интересно, я оставил вопрос здесь , чтобы понять , почему это так.
Джош
2

Это меня тоже смутило. Вот пример, который я сделал, пытаясь настроить генератор, который выдает и принимает сигналы в чередующемся порядке (yield, accept, yield, accept) ...

def echo_sound():

    thing_to_say = '<Sound of wind on cliffs>'
    while True:
        thing_to_say = (yield thing_to_say)
        thing_to_say = '...'.join([thing_to_say]+[thing_to_say[-6:]]*2)
        yield None  # This is the return value of send.

gen = echo_sound()

print 'You are lost in the wilderness, calling for help.'

print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Hello!'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)

print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Is anybody out there?'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)

print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Help!'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)

Выход:

You are lost in the wilderness, calling for help.
------
You hear: "<Sound of wind on cliffs>"
You yell "Hello!"
------
You hear: "Hello!...Hello!...Hello!"
You yell "Is anybody out there?"
------
You hear: "Is anybody out there?...there?...there?"
You yell "Help!"
Питер
источник