finalidade da função "send" do gerador python?


165

Alguém pode me dar um exemplo de por que existe a função "send" associada à função de gerador Python? Eu entendo perfeitamente a função de rendimento. No entanto, a função de envio é confusa para mim. A documentação deste método é complicada:

generator.send(value)

Retoma a execução e "envia" um valor para a função do gerador. O argumento do valor se torna o resultado da expressão de rendimento atual. O método send () retorna o próximo valor gerado pelo gerador ou gera StopIteration se o gerador sair sem gerar outro valor.

O que isso significa? Eu pensei que valor era a entrada para a função? A frase "O método send () retorna o próximo valor gerado pelo gerador" também parece ser o objetivo exato da função yield; yield retorna o próximo valor gerado pelo gerador ...

Alguém pode me dar um exemplo de gerador utilizando send que realiza algo que o rendimento não pode?



3
Acrescentou outro exemplo da vida real (lendo de FTP), quando as chamadas de retorno são transformados em gerador usados de dentro
Jan Vlcinsky

2
Vale ressaltar que "Quando send()é chamado para iniciar o gerador, ele deve ser chamado com Noneo argumento, porque não há expressão de rendimento que possa receber o valor.", Citado no documento oficial e para o qual a citação na pergunta é ausência de.
Rick

Respostas:


147

É usado para enviar valores para um gerador que acabou de render. Aqui está um exemplo explicativo artificial (não útil):

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

Você não pode fazer isso apenas com yield.

Quanto à utilidade, um dos melhores casos de uso que eu já vi é o Twisted @defer.inlineCallbacks. Essencialmente, ele permite que você escreva uma função como esta:

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

O que acontece é que o takesTwoSeconds()retorno a Deferred, que é um valor que promete um valor, será computado posteriormente. Twisted pode executar o cálculo em outro thread. Quando o cálculo é feito, ele é transferido para o adiado, e o valor é enviado de volta à doStuff()função. Assim, o doStuff()pode acabar parecendo mais ou menos com uma função processual normal, exceto que pode estar fazendo todos os tipos de cálculos e retornos de chamada, etc. A alternativa antes dessa funcionalidade seria fazer algo como:

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

É muito mais complicado e pesado.


2
Você pode explicar qual é o objetivo disso? Por que isso não pode ser recriado com double_inputs (número de partida) e rendimento?
Tommy #

@ Tommy: oh, porque os valores que você tem não tem nada a ver com o anterior. deixe-me mudar o exemplo
Claudiu 10/10

por que você usaria isso então em uma função simples que dobra sua entrada?
Tommy #

4
@ Tommy: Você não faria. O primeiro exemplo é apenas para explicar o que faz. O segundo exemplo é para um caso de uso realmente útil.
Cláudio

1
@ Tommy: Eu diria que se você realmente quer saber, confira esta apresentação e resolva tudo. Uma resposta curta não será suficiente, porque você dirá "Mas não posso fazer assim?" etc.
Claudiu 10/10

96

Esta função é escrever corotinas

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

impressões

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

Veja como o controle está sendo passado para frente e para trás? Essas são corotinas. Eles podem ser usados ​​para todos os tipos de coisas legais, como assíncrono IO e similares.

Pense assim, com um gerador e sem envio, é uma rua de mão única

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

Mas com o envio, torna-se uma via de mão dupla

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

O que abre a porta para o usuário personalizar o comportamento dos geradores em tempo real e o gerador responder ao usuário.


3
mas uma função de gerador pode receber parâmetros. Como o "Send" vai além do envio de um parâmetro para o gerador?
Tommy #

13
@ Tommy Porque você não pode alterar os parâmetros para um gerador enquanto ele é executado. Você fornece parâmetros, é executado, pronto. Com envio, você dá parâmetros, ele é executado por um pouco, você envia-lhe um valor e que o faz diferente algo, repita
Daniel Gratzer

2
@Tommy Isto irá reiniciar o gerador, que fará com que você refazer muito trabalho
Daniel Gratzer

5
Você poderia explicar o propósito de enviar um None antes de tudo?
Shubham Aggarwal

2
@ShubhamAggarwal Isso é feito para 'iniciar' o gerador. É apenas algo que precisa ser feito. Faz algum sentido quando você pensa sobre isso desde a primeira vez que você liga para send()o gerador ainda não alcançou a palavra-chave yield.
Michael

50

Isso pode ajudar alguém. Aqui está um gerador que não é afetado pela função de envio. Ele recebe o parâmetro number na instanciação e não é afetado pelo envio:

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

Agora, aqui está como você faria o mesmo tipo de função usando send, para que em cada iteração você possa alterar o valor do número:

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

Aqui está o que parece, pois você pode ver que o envio de um novo valor para number altera o resultado:

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

Você também pode colocar isso em um loop for, como tal:

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

Para obter mais ajuda, consulte este ótimo tutorial .


12
Essa comparação entre uma função que não é afetada pelo send () com outra que realmente ajudou. Obrigado!
precisa saber é o seguinte

Como isso pode ser um exemplo ilustrativo do objetivo send? Um simples lambda x: x * 2faz a mesma coisa de uma maneira muito menos complicada.
user209974 13/03

Ele usa send? Vá e adicione sua resposta.
radtek 13/03

17

Alguns casos de uso para usar gerador e send()

Geradores com send()permissão:

  • lembrando o estado interno da execução
    • em que etapa estamos
    • qual é o status atual dos nossos dados
  • retornando sequência de valores
  • recebendo sequência de entradas

Aqui estão alguns casos de uso:

Tentativa observada de seguir uma receita

Vamos ter uma receita, que espera um conjunto predefinido de entradas em alguma ordem.

Podemos:

  • crie uma watched_attemptinstância da receita
  • deixe receber algumas entradas
  • com cada entrada, retorne informações sobre o que está atualmente no pote
  • com cada verificação de entrada, se a entrada é a esperada (e falhará se não for)

    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
    

Para usá-lo, primeiro crie a watched_attemptinstância:

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

A chamada para .next()é necessária para iniciar a execução do gerador.

O valor retornado é exibido, nosso pote está vazio no momento.

Agora, faça poucas ações seguindo o que a receita espera:

>>> 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"))                                                                            
[] 

Como vemos, o pote está finalmente vazio.

Caso não se seguisse a receita, ela falharia (o que poderia ser o resultado desejado da tentativa de cozinhar algo - apenas aprendendo que não prestamos atenção suficiente quando recebemos instruções.

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

Notar que:

  • existe uma sequência linear de etapas esperadas
  • os passos podem diferir (alguns estão removendo, outros estão adicionando à panela)
  • nós conseguimos fazer tudo isso por meio de uma função / gerador - não há necessidade de usar classes complexas ou estruturas semelhantes.

Totais em execução

Podemos usar o gerador para acompanhar o total de valores enviados a ele.

Sempre que adicionamos um número, a contagem de entradas e a soma total são retornadas (válidas para o momento em que a entrada anterior foi enviada a ela).

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)

A saída seria semelhante a:

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
Eu corro o seu exemplo e no python 3 parece que o watch_attempt.next () deve ser substituído por next (watching_attempt).
thanos.a

15

O send()método controla qual será o valor à esquerda da expressão de rendimento.

Para entender como o rendimento difere e qual o valor que ele possui, vamos primeiro atualizar rapidamente a ordem em que o código python é avaliado.

Seção 6.15 Ordem de avaliação

Python avalia expressões da esquerda para a direita. Observe que, ao avaliar uma tarefa, o lado direito é avaliado antes do lado esquerdo.

Portanto, uma expressão a = bdo lado direito é avaliada primeiro.

Como demonstrado a seguir, a[p('left')] = p('right')o lado direito é avaliado primeiro.

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

O que yield produz ?, produz, suspende a execução da função e retorna ao chamador e retoma a execução no mesmo local em que parou antes da suspensão.

Onde exatamente a execução está suspensa? Você já deve ter adivinhado ... a execução está suspensa entre o lado direito e esquerdo da expressão de rendimento. Portanto, new_val = yield old_vala execução é interrompida no =sinal e o valor à direita (que é antes da suspensão e também o valor retornado ao chamador) pode ser algo diferente do valor à esquerda (que é o valor que está sendo atribuído após a retomada) execução).

yield produz 2 valores, um para a direita e outro para a esquerda.

Como você controla o valor no lado esquerdo da expressão de rendimento? através do .send()método

6.2.9 Expressões de rendimento

O valor da expressão de rendimento após a retomada depende do método que reiniciou a execução. Se __next__()for usado (normalmente via a for ou next()builtin), o resultado será Nenhum. Caso contrário, se send()for usado, o resultado será o valor passado para esse método.


13

O sendmétodo implementa corotinas .

Se você não encontrou as Coroutines, elas são complicadas, porque mudam a maneira como o programa flui. Você pode ler um bom tutorial para mais detalhes.


6

A palavra "rendimento" tem dois significados: produzir algo (por exemplo, produzir milho) e parar para deixar que outra pessoa continue (por exemplo, carros cedendo a pedestres). Ambas as definições se aplicam à yieldpalavra-chave do Python ; o que torna as funções do gerador especiais é que, diferentemente das funções regulares, os valores podem ser "retornados" ao chamador enquanto apenas pausam, e não encerram, uma função do gerador.

É mais fácil imaginar um gerador como uma extremidade de um tubo bidirecional com uma extremidade "esquerda" e uma extremidade "direita"; esse tubo é o meio sobre o qual os valores são enviados entre o próprio gerador e o corpo da função do gerador. Cada extremidade do tubo possui duas operações push:, que envia um valor e bloqueia até a outra extremidade do tubo extrair o valor e não retornar nada; epull, que bloqueia até a outra extremidade do tubo empurra um valor e retorna o valor pressionado. No tempo de execução, a execução alterna entre os contextos de cada lado do canal - cada lado corre até enviar um valor para o outro lado; nesse ponto, ele pára, deixa o outro lado correr e aguarda um valor em return, altura em que o outro lado pára e continua. Em outras palavras, cada extremidade do canal passa do momento em que recebe um valor até o momento em que envia um valor.

O tubo é funcionalmente simétrico, mas - por convenção que estou definindo nesta resposta - a extremidade esquerda está disponível apenas dentro do corpo da função do gerador e é acessível através da yieldpalavra - chave, enquanto a extremidade direita é o gerador e está acessível através do sendfunção do gerador . Como interfaces singulares para suas respectivas extremidades do tubo, yielde sendcumprem um duplo dever: ambos empurram e puxam valores de / para suas extremidades do tubo, yieldempurrando para a direita e puxando para a esquerda, enquanto sendfaz o oposto. Esse duplo dever é o cerne da confusão em torno da semântica de declarações como x = yield y. Dividir yielde senddividir em duas etapas explícitas de push / pull tornará sua semântica muito mais clara:

  1. Suponha que gseja o gerador. g.sendempurra um valor para a esquerda pela extremidade direita do tubo.
  2. Execução no contexto de gpausas, permitindo que o corpo da função do gerador funcione.
  3. O valor pressionado g.sendé puxado para a esquerda yielde recebido na extremidade esquerda do tubo. In x = yield y, xé atribuído ao valor extraído.
  4. A execução continua dentro do corpo da função do gerador até que a próxima linha que contém yieldseja alcançada.
  5. yieldempurra um valor para a direita pela extremidade esquerda do tubo, de volta para g.send. Em x = yield y, yé empurrado para a direita através do tubo.
  6. A execução dentro do corpo da função do gerador faz uma pausa, permitindo que o escopo externo continue de onde parou.
  7. g.send retoma e puxa o valor e o retorna ao usuário.
  8. Quando g.sendé a próxima chamada, volte para a Etapa 1.

Embora cíclico, esse procedimento tem um começo: quando g.send(None)- o que é next(g)abreviado - é chamado pela primeira vez (é ilegal passar algo diferente Noneda primeira sendchamada). E pode ter um fim: quando não houver mais yieldinstruções a serem alcançadas no corpo da função do gerador.

Você vê o que torna a yielddeclaração (ou mais precisamente, geradores) tão especial? Ao contrário da returnpalavra-chave desprezível , yieldé capaz de passar valores para o chamador e receber valores do chamador sem interromper a função em que ele vive! (Obviamente, se você deseja encerrar uma função - ou um gerador - também é útil ter a returnpalavra - chave.) Quando uma yieldinstrução é encontrada, a função do gerador apenas faz uma pausa e, em seguida, retoma exatamente onde estava off ao receber outro valor. E sendé apenas a interface para se comunicar com a parte interna de uma função geradora de fora dela.

Se realmente queremos quebrar esse push / pull / analogia tubo para baixo, tanto quanto pudermos, vamos acabar com o seguinte pseudocódigo que realmente impulsiona casa que, além de etapas 1-5, yielde sendsão dois lados da mesma moeda pipe:

  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

A transformação fundamental é que temos dividido x = yield ye value1 = g.send(value2)cada um em duas instruções: left_end.push(y)e x = left_end.pull(); e value1 = right_end.pull()e right_end.push(value2). Existem dois casos especiais da yieldpalavra-chave: x = yielde yield y. Estes são açúcar sintático, respectivamente, para x = yield Nonee _ = yield y # discarding value.

Para detalhes específicos sobre a ordem precisa em que os valores são enviados através do canal, veja abaixo.


O que se segue é um modelo concreto bastante longo do anterior. Primeiro, deve-se notar que, para qualquer gerador g, next(g)é exatamente equivalente a g.send(None). Com isso em mente, podemos nos concentrar apenas em como sendfunciona e conversar apenas sobre o avanço do gerador send.

Suponha que tenhamos

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

Agora, a definição de fdesugars para a seguinte função comum (não geradora):

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

O que aconteceu nesta transformação de f:

  1. Movemos a implementação para uma função aninhada.
  2. Criamos um canal bidirecional cujo left_endacesso pela função aninhada e cujo right_endretorno e acesso pelo escopo externo - right_endé o que conhecemos como objeto gerador.
  3. Dentro da função aninhada, a primeira coisa que fazemos é verificar que left_end.pull()é None, consumindo um valor empurrado no processo.
  4. Dentro da função aninhada, a instrução x = yield yfoi substituída por duas linhas: left_end.push(y)e x = left_end.pull().
  5. Definimos a sendfunção para right_end, que é a contrapartida das duas linhas pelas quais substituímos a x = yield yinstrução na etapa anterior.

Neste mundo de fantasia, onde as funções podem continuar após o retorno, gsão atribuídas right_ende depois impl()são chamadas. Portanto, em nosso exemplo acima, se seguíssemos a execução linha por linha, o que aconteceria é aproximadamente o seguinte:

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

Isso é mapeado exatamente para o pseudocódigo de 16 etapas acima.

Existem outros detalhes, como a propagação de erros e o que acontece quando você chega ao final do gerador (o tubo está fechado), mas isso deve deixar claro como o fluxo de controle básico funciona quando sendé usado.

Usando essas mesmas regras de remoção de açúcar, vejamos dois casos especiais:

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

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

Na maioria das vezes, eles desejam a mesma maneira que f, as únicas diferenças são como as yielddeclarações são transformadas:

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

No primeiro, o valor passado para f1é empurrado (gerado) inicialmente e, em seguida, todos os valores puxados (enviados) são empurrados (retornados) de volta. No segundo, xainda não tem valor quando chega o momento push, então um UnboundLocalErroré aumentado.


"O argumento 1 em g = f (1) foi capturado normalmente e atribuído a y no corpo de f, mas o tempo True ainda não começou." Por que não? Por que o Python não tentaria executar esse código até encontrar, por exemplo yield?
Josh

@ Josh O cursor não é avançado até a primeira chamada para send; leva uma chamada de send(None)para mover o cursor para a primeira yieldinstrução e somente então as sendchamadas subsequentes realmente enviam um valor "real" para yield.
BallpointBen

Obrigado - Isso é interessante, para que o intérprete saiba que a função f será yield em algum momento e, portanto, aguarde até receber uma sendchamada do chamador? Com uma função normal, o intérprete começaria a executar fimediatamente, certo? Afinal, não há compilação de AOT de qualquer tipo no Python. Tem certeza de que é o caso? (sem questionar o que você está dizendo, estou realmente intrigado com o que você escreveu aqui). Onde posso ler mais sobre como o Python sabe que precisa esperar antes de começar a executar o restante da função?
Josh

@ Josh Eu construí esse modelo mental apenas observando como os diferentes geradores de brinquedos funcionam, sem entender os componentes internos do Python. No entanto, o fato de a inicial send(None)produzir o valor apropriado (por exemplo, 1) sem enviar Nonepara o gerador sugere que a primeira chamada para sendé um caso especial. É uma interface complicada para projetar; se você permitir que o primeiro sendenvie um valor arbitrário, a ordem dos valores gerados e dos valores enviados será reduzida em um em comparação com o que é atualmente.
BallpointBen

Obrigado BallpointBen. Muito interessante, deixei uma pergunta aqui para ver por que é esse o caso.
Josh

2

Isso também me confundiu. Aqui está um exemplo que eu fiz ao tentar configurar um gerador que gera e aceita sinais em ordem alternada (rendimento, aceitação, produção, aceitação) ...

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)

A saída é:

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!"
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.