генератор пітонів «відправити» призначення функції?


164

Чи може хтось надати мені приклад, чому існує функція "відправити", пов'язана з функцією генератора Python? Я повністю розумію функцію врожайності. Однак функція надсилання мене бентежить. Документація щодо цього методу складена:

generator.send(value)

Відновлює виконання та "відправляє" значення у функцію генератора. Аргумент значення стає результатом поточного вираження урожайності. Метод send () повертає наступне значення, отримане генератором, або підвищує StopIteration, якщо генератор виходить, не даючи іншого значення.

Що це означає? Я думав, що значення є входом до функції? Фраза "метод send () повертає наступне значення, отримане генератором", здається, також є точною метою функції врожаю; врожайність повертає наступне значення, отримане генератором ...

Чи може хтось надати мені приклад генератора, що використовує відправлення, що забезпечує щось, вихід не може?



3
Додано ще один приклад із реального життя (читання з FTP), коли зворотні виклики перетворюються на генератор, що використовується зсередини
Ян Влчінський,

2
Варто згадати, що "Коли send()викликається запуск генератора, його слід викликати Noneяк аргумент, оскільки немає виразу виходу, який міг би отримати значення.", Цитований з офіційного документа і для якого цитування у питанні є зниклий безвісти.
Рік

Відповіді:


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()повернення a Deferred, що є величиною, що обіцяє значення, буде обчислено пізніше. Скручений може запустити обчислення в інший потік. Коли обчислення зроблено, він передає його у відкладене, а значення потім надсилається назад до 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 (startnumber) та дохідності?
Томмі

@Tommy: о, оскільки отримані значення не мають нічого спільного з попереднім. дозвольте мені змінити приклад
Клавдіу

чому б ви скористалися цим способом над простою функцією, що подвоює його вхід ??
Томмі

4
@Tommy: Ви б не стали. Перший приклад - просто пояснити, що це робить. Другий приклад - справді корисний випадок використання.
Клавдіу

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

Подивіться, як контроль передається вперед і назад? Це супроводи. Вони можуть бути використані для будь-яких класних речей, таких як асинхронний IO тощо.

Подумайте про це так, з генератором і без надсилання, це дорога в одну сторону

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

Але з відправленням вона стає двосторонньою вулицею

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

Що відкриває користувачеві двері для налаштування поведінки генераторів на льоту та генератора, що відповідає користувачеві.


3
але функція генератора може приймати параметри. Як "Надіслати" виходить за межі надсилання параметра генератору?
Томмі

13
@Tommy Тому що ви не можете змінити параметри генератору під час його роботи. Ви даєте йому параметри, він працює, зроблено. При відправці ви даєте йому параметри, вона працює трохи, ви надсилаєте їй значення, і воно робить щось інше, повторіть
Daniel Gratzer

2
@Tommy Це перезапустить генератор, що змусить вас переробити багато роботи
Daniel Gratzer

5
Чи можете ви, будь ласка, пояснити мету надсилання «Ніхто» перед усім?
Shubham Aggarwal

2
@ShubhamAggarwal Це робиться для 'запуску' генератора. Це просто щось, що потрібно зробити. Це має сенс, коли ви думаєте про це з першого разу, коли ви зателефонували send()до генератора, ще не дійшли до ключового слова yield.
Майкл

50

Це може комусь допомогти. Ось генератор, на який функція відправлення не впливає. Він бере параметр числа при створенні даних і на нього не впливає відправлення:

>>> 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, тож на кожній ітерації ви можете змінити значення числа:

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

Для отримання додаткової інформації ознайомтеся з цим чудовим посібником .


12
Це порівняння між функцією, на яку send () не впливає, з тією, що робить, справді допомогло. Дякую!
Манас Баджай

Як це може бути наочним прикладом мети send? Простий lambda x: x * 2робить те саме в набагато менш перекрученому вигляді.
користувач209974

Він використовує send? Ідіть і додайте свою відповідь.
radtek

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 здається, що watch_attempt.next () має бути замінено на next (watch_attempt).
thanos.a

15

У send()контролюєте методу , що значення ліворуч від виразу плинності буде.

Щоб зрозуміти, чим відрізняється дохідність і яке значення вона містить, спершу швидше оновлюємо код питона замовлення.

Розділ 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]

Що робить урожайність ?, урожайність, призупиняє виконання функції та повертається до абонента, а також відновлює виконання у тому самому місці, яке було припинено до призупинення.

Де саме призупинення виконання призупинено? Ви, можливо, вже здогадалися ... виконання виконується призупинено між правою та лівою частиною виразу виходу. Отже new_val = yield old_valвиконання зупиняється на =знаку, і значення праворуч (яке є перед тимчасовим призупиненням, а також значення, повернене абоненту) може бути дещо іншим, ніж значення зліва (яке значення присвоюється після відновлення виконання).

yield дає 2 значення, одне праворуч і інше ліворуч.

Як ви керуєте значенням у лівій частині виразу виходу? за допомогою .send()методу.

6.2.9. Вирази виходу

Значення виразу виходу після відновлення залежить від способу, який відновив виконання. Якщо __next__()використовується (як правило, або для next()вбудованого або для вбудованого), результат є Немає. В іншому випадку, якщо send()використовується, то результатом буде значення, передане цьому методу.



6

Слово "врожайність" має два значення: виробляти щось (наприклад, давати урожай кукурудзи) і зупиняти, щоб хтось / щось інше продовжував (наприклад, машини, що піддаються пішоходам). Обидва визначення стосуються Python'syield ключового слова ; Що робить функції генератора особливими, це те, що на відміну від звичайних функцій, значення можуть бути "повернені" абоненту, просто призупинивши, не припиняючи, функцію генератора.

Найпростіше уявити генератор як один кінець двонаправленої труби з "лівим" кінцем і "правим" кінцем; ця труба є середовищем, над яким надсилаються значення між самим генератором і тілом функції генератора. Кожен кінець труби має дві операції: pushяка надсилає значення і блокує, поки інший кінець труби не витягне значення і нічого не поверне; іpull, який блокує, поки інший кінець труби не натисне значення, і повертає висунуте значення. Під час виконання виконання виконує відхилення вперед і назад між контекстами з обох боків труби - кожна сторона працює, поки не надішле значення іншій стороні, після чого вона зупиняється, дозволяє іншій стороні бігати і чекає значення в повернення, після чого інша сторона зупиняється і вона поновлюється. Іншими словами, кожен кінець труби проходить з моменту отримання значення до моменту, коли він надсилає значення.

Труба функціонально симетрична, але - за умовою, яку я визначаю у цій відповіді - лівий кінець доступний лише у тілі функції генератора і доступний за допомогою yieldключового слова, тоді як правий кінець - генератор і доступний через sendфункція генератора . Як єдині інтерфейси до відповідних кінців труби, yieldі sendвиконують подвійний обов'язок: вони обидва штовхають і тягнуть значення до / з кінців труби, yieldштовхаючи вправо і тягнучи вліво, роблячи sendнавпаки. Цей подвійний обов'язок є сутністю плутанини навколо семантики висловлювань x = yield y. Розбивання yieldта sendзменшення двох чітких кроків "push / pull" зробить їх семантику набагато зрозумілішою:

  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

Здебільшого вони дегуртують так само, як 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піднімається.


"Аргумент 1 в g = f (1) був зафіксований нормально і призначений y в тілі f, але поки True не почався ще." Чому ні? Чому б Python не спробував запустити цей код, поки він не зіткнеться, наприклад yield?
Джош

@Josh Курсор не просунутий до першого дзвінка до send; send(None)для переміщення курсору до першого yieldоператора потрібно один виклик , і лише після цього наступні sendдзвінки фактично надсилають "реальне" значення yield.
BallpointBen

Спасибі - Це цікаво, тому перекладач знає, що функція f буде yield в якийсь момент, і, таким чином, чекати, поки вона не отримає sendвід абонента? При нормальному функціонуванні перекладач просто почне виконувати їх fвідразу, правда? Зрештою, у Python немає жодної компіляції AOT. Ви впевнені, що це так? (не ставлячи під сумнів те, що ви говорите, я справді просто спантеличений тим, що ви тут написали). Де я можу прочитати більше про те, як Python знає, що йому потрібно почекати, перш ніж він почне виконувати решту функції?
Джош

@Josh Я створив цю ментальну модель лише спостерігаючи за тим, як працюють різні генератори іграшок, не розуміючи внутрішніх справ Python. Однак той факт, що початковий send(None)дає відповідне значення (наприклад, 1) без надсилання Noneв генератор, говорить про те, що перший виклик до send- це особливий випадок. Це складний інтерфейс для проектування; якщо ви дозволите першому sendнадсилати довільне значення, то порядок виведених значень та надісланих значень буде вимкнено на одиницю порівняно з тим, що є в даний час.
BallpointBen

Дякую BallpointBen. Дуже цікаво, я залишив тут питання, щоб побачити, чому це так.
Джош

2

Ці мене також бентежили. Ось приклад, який я зробив, намагаючись налаштувати генератор, який видає та приймає сигнали в чергуванні (вихід, прийняття, вихід, прийняття) ...

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!"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.