scopo della funzione "send" del generatore python?


165

Qualcuno può darmi un esempio del perché esiste la funzione "invia" associata alla funzione del generatore Python? Comprendo appieno la funzione di rendimento. Tuttavia, la funzione di invio mi confonde. La documentazione su questo metodo è contorta:

generator.send(value)

Riprende l'esecuzione e "invia" un valore nella funzione generatore. L'argomento value diventa il risultato dell'espressione di rendimento corrente. Il metodo send () restituisce il valore successivo prodotto dal generatore o genera StopIteration se il generatore esce senza produrre un altro valore.

Cosa significa? Ho pensato che il valore fosse l'input per la funzione? La frase "Il metodo send () restituisce il valore successivo prodotto dal generatore" sembra essere anche lo scopo esatto della funzione yield; yield restituisce il valore successivo prodotto dal generatore ...

Qualcuno può darmi un esempio di un generatore che utilizza send che compie qualcosa che la resa non può?



3
Aggiunto un altro esempio di vita reale (lettura da FTP) quando i callback vengono trasformati in generatore utilizzato dall'interno
Jan Vlcinsky,

2
Vale la pena ricordare che "Quando send()viene chiamato per avviare il generatore, deve essere chiamato Nonecome argomento, perché non esiste un'espressione di rendimento che potrebbe ricevere il valore", citato dal documento ufficiale e per il quale la citazione nella domanda è mancante.
Rick,

Risposte:


147

È usato per inviare valori in un generatore che ha appena ceduto. Ecco un esempio esplicativo artificiale (non utile):

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

Non puoi farlo solo con yield.

Per quanto riguarda il motivo per cui è utile, uno dei migliori casi d'uso che ho visto è quello di Twisted @defer.inlineCallbacks. In sostanza ti permette di scrivere una funzione come questa:

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

Quello che succede è che takesTwoSeconds()restituisce a Deferred, che è un valore che promette che un valore verrà calcolato in seguito. Twisted può eseguire il calcolo in un altro thread. Al termine del calcolo, lo passa nel differito e il valore viene quindi rimandato alla doStuff()funzione. Quindi doStuff()può sembrare più o meno come una normale funzione procedurale, tranne che può fare tutti i tipi di calcoli e callback ecc. L'alternativa prima di questa funzionalità sarebbe fare qualcosa del tipo:

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

È molto più contorto e ingombrante.


2
Puoi spiegare qual è lo scopo di questo? Perché questo non può essere ricreato con double_inputs (numero di partenza) e rendimento?
Tommy,

@ Tommy: oh perché i valori che hai non hanno nulla a che fare con quello precedente. fammi cambiare l'esempio
Claudiu,

perché dovresti usarlo poi con una semplice funzione che raddoppia il suo input ??
Tommy,

4
@ Tommy: Non lo faresti. Il primo esempio è solo per spiegare cosa fa. Il secondo esempio è per un caso d'uso effettivamente utile.
Claudiu,

1
@ Tommy: direi se vuoi davvero sapere dai un'occhiata a questa presentazione e lavorare su tutto. Una breve risposta non sarà sufficiente perché poi dirai "Ma non posso farlo in questo modo?" ecc.
Claudiu,

96

Questa funzione è di scrivere coroutine

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

stampe

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

Vedi come il controllo viene passato avanti e indietro? Quelle sono coroutine. Possono essere utilizzati per tutti i tipi di cose interessanti come l'asynch IO e simili.

Pensala in questo modo, con un generatore e nessun invio, è una strada a senso unico

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

Ma con send diventa una strada a doppio senso

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

Ciò apre la porta all'utente personalizzando il comportamento dei generatori al volo il e il generatore che risponde all'utente.


3
ma una funzione del generatore può accettare parametri. In che modo "Invia" va oltre l'invio di un parametro al generatore?
Tommy,

13
@ Tommy Perché non è possibile modificare i parametri in un generatore mentre è in esecuzione. Gli dai parametri, funziona, fatto. Con send, gli dai parametri, viene eseguito per un po ', gli invii un valore e fa qualcosa di diverso, ripeti
Daniel Gratzer

2
@ Tommy Questo riavvierà il generatore, il che ti farà rifare molto lavoro
Daniel Gratzer,

5
Potresti spiegare per favore lo scopo di inviare un None prima di tutto?
Shubham Aggarwal,

2
@ShubhamAggarwal Viene fatto per 'avviare' il generatore. È solo qualcosa che deve essere fatto. Ha senso se ci pensi dalla prima volta che chiami send()il generatore non ha ancora raggiunto la parola chiave yield.
Michael,

50

Questo può aiutare qualcuno. Ecco un generatore che non è interessato dalla funzione di invio. Accetta il parametro numerico all'istanza e non viene influenzato da 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

Ora ecco come faresti lo stesso tipo di funzione usando send, quindi ad ogni iterazione puoi cambiare il valore del numero:

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

Ecco come appare, come puoi vedere l'invio di un nuovo valore per il numero cambia il risultato:

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

Puoi anche inserirlo in un ciclo for in quanto tale:

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

Per ulteriori informazioni, dai un'occhiata a questo fantastico tutorial .


12
Questo confronto tra una funzione che non viene influenzata da send () con uno che lo fa, ha davvero aiutato. Grazie!
Manas Bajaj,

Come può essere un esempio illustrativo dello scopo di send? Un semplice lambda x: x * 2fa la stessa cosa in un modo molto meno contorto.
user209974

Usa invia? Vai e aggiungi la tua risposta.
Radtek,

17

Alcuni casi d'uso per l'utilizzo di generatore e send()

I generatori con send()consentono:

  • ricordare lo stato interno dell'esecuzione
    • a che punto siamo
    • qual è lo stato attuale dei nostri dati
  • sequenza di valori di ritorno
  • ricezione sequenza di ingressi

Ecco alcuni casi d'uso:

Ho provato a seguire una ricetta

Diamo una ricetta, che prevede un set predefinito di input in un certo ordine.

Potremmo:

  • creare watched_attemptun'istanza dalla ricetta
  • lascia che ottenga alcuni input
  • con ogni input restituisce informazioni su ciò che è attualmente nel piatto
  • con ogni controllo di input, che l'input è quello previsto (e fallisce se non lo è)

    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

Per usarlo, prima crea l' watched_attemptistanza:

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

La chiamata a .next()è necessaria per avviare l'esecuzione del generatore.

Il valore restituito mostra, il nostro piatto è attualmente vuoto.

Ora esegui alcune azioni seguendo le aspettative della ricetta:

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

Come vediamo, il piatto è finalmente vuoto.

Nel caso in cui uno non seguirebbe la ricetta, fallirebbe (ciò che potrebbe essere il risultato desiderato del tentativo di cucinare qualcosa visto - solo imparando che non abbiamo prestato abbastanza attenzione quando ci hanno dato le istruzioni.

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

Notare che:

  • c'è una sequenza lineare di passi previsti
  • i passaggi possono differire (alcuni stanno rimuovendo, altri stanno aggiungendo al piatto)
  • riusciamo a fare tutto questo da un generatore / funzioni - non c'è bisogno di usare classi complesse o strutture simili.

Totali in esecuzione

Possiamo utilizzare il generatore per tenere traccia del totale corrente dei valori inviati ad esso.

Ogni volta che aggiungiamo un numero, il conteggio degli input e la somma totale vengono restituiti (valido per il momento in cui è stato inviato l'input precedente).

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)

L'output sarebbe simile 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
Eseguo il tuo esempio e in Python 3 sembra che watch_attempt.next () debba essere sostituito da next (watch_attempt).
Grazie

15

Il send()metodo controlla quale sarà il valore a sinistra dell'espressione di rendimento.

Per capire in che modo differisce la resa e quale valore contiene, consente innanzitutto di aggiornare rapidamente il codice Python dell'ordine da valutare.

Sezione 6.15 Ordine di valutazione

Python valuta le espressioni da sinistra a destra. Si noti che durante la valutazione di un compito, il lato destro viene valutato prima del lato sinistro.

Quindi un'espressione a = b sul lato destro viene valutata per prima.

Come segue dimostra che a[p('left')] = p('right')il lato destro viene valutato per primo.

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

Cosa fa yield ?, yield, sospende l'esecuzione della funzione e torna al chiamante e riprende l'esecuzione nello stesso punto in cui era stata interrotta prima della sospensione.

Dove è esattamente sospesa l'esecuzione? Potresti averlo già indovinato ... l' esecuzione è sospesa tra il lato destro e sinistro dell'espressione di rendimento. Cosìnew_val = yield old_val l'esecuzione viene interrotta dal =segno e il valore a destra (che è prima di sospendere, ed è anche il valore restituito al chiamante) potrebbe essere qualcosa di diverso dal valore a sinistra (che è il valore assegnato dopo la ripresa esecuzione).

yield restituisce 2 valori, uno a destra e l'altro a sinistra.

Come si controlla il valore sul lato sinistro dell'espressione di rendimento? tramite il.send() metodo

6.2.9. Espressioni di resa

Il valore dell'espressione di rendimento dopo la ripresa dipende dal metodo che ha ripreso l'esecuzione. Se __next__()viene utilizzato (in genere tramite un for o il next()builtin), il risultato è Nessuno. Altrimenti, se send()viene utilizzato, il risultato sarà il valore passato a quel metodo.


13

Il sendmetodo implementa le coroutine .

Se non hai incontrato Coroutine, sono difficili da avvolgere perché cambiano il modo in cui scorre un programma. Puoi leggere un buon tutorial per maggiori dettagli.


6

La parola "resa" ha due significati: produrre qualcosa (ad esempio, produrre mais) e fermarsi per far continuare qualcun altro (ad esempio, automobili che cedono ai pedoni). Entrambe le definizioni si applicano a Pythonyield parola chiave ; ciò che rende speciali le funzioni del generatore è che, diversamente dalle normali funzioni, i valori possono essere "restituiti" al chiamante semplicemente mettendo in pausa, non terminando, una funzione generatore.

È più facile immaginare un generatore come un'estremità di un tubo bidirezionale con un'estremità "sinistra" e un'estremità "destra"; questa pipe è il mezzo su cui vengono inviati i valori tra il generatore stesso e il corpo della funzione del generatore. Ciascuna estremità del tubo ha due operazioni push:, che invia un valore e si blocca fino a quando l'altra estremità del tubo tira il valore e non restituisce nulla; epull, che si blocca fino a quando l'altra estremità del tubo non spinge un valore e restituisce il valore spinto. In fase di esecuzione, l'esecuzione rimbalza avanti e indietro tra i contesti su entrambi i lati del tubo: ogni lato viene eseguito fino a quando non invia un valore all'altro lato, a quel punto si ferma, lascia correre l'altro lato e attende un valore in ritorna, a quel punto l'altro lato si ferma e riprende. In altre parole, ciascuna estremità del tubo scorre dal momento in cui riceve un valore al momento in cui invia un valore.

La pipe è funzionalmente simmetrica, ma - per convenzione sto definendo in questa risposta - l'estremità sinistra è disponibile solo all'interno del corpo della funzione del generatore ed è accessibile tramite la yieldparola chiave, mentre l'estremità destra è il generatore ed è accessibile tramite il sendfunzione del generatore . Come interfacce singolari alle loro rispettive estremità del tubo, yielde sendfanno il doppio dovere: entrambi spingono e tirano entrambi i valori da / verso le loro estremità del tubo, yieldspingendo verso destra e tirando verso sinistra mentre sendfa l'opposto. Questo doppio dovere è il punto cruciale della confusione che circonda la semantica di affermazioni simili x = yield y. Rottura yielde sendin due fasi esplicito push / pull renderà la loro semantica molto più chiaro:

  1. Supponiamo che gsia il generatore. g.sendspinge un valore verso sinistra attraverso l'estremità destra del tubo.
  2. Esecuzione nel contesto di gpause, consentendo l'esecuzione del corpo della funzione generatore.
  3. Il valore spinto da g.sendviene tirato a sinistra da yielde ricevuto sull'estremità sinistra del tubo. In x = yield y, xviene assegnato al valore estratto.
  4. L'esecuzione continua all'interno del corpo della funzione generatore fino a raggiungere la riga successiva contenente yield.
  5. yieldspinge un valore verso destra attraverso l'estremità sinistra del tubo, di nuovo su g.send. In x = yield y, yviene spinto a destra attraverso il tubo.
  6. L'esecuzione all'interno del corpo della funzione generatore si interrompe, consentendo all'oscilloscopio esterno di continuare da dove era stato interrotto.
  7. g.send riprende e tira il valore e lo restituisce all'utente.
  8. Alla g.sendsuccessiva chiamata, tornare al passaggio 1.

Sebbene ciclica, questa procedura ha un inizio: quando g.send(None)- che è l' next(g)abbreviazione - viene chiamato per la prima volta (è illegale passare qualcosa di diverso rispetto Nonealla prima sendchiamata). E può avere una fine: quando non ci sono più yielddichiarazioni da raggiungere nel corpo della funzione generatore.

Vedi cosa rende yieldcosì speciale l' affermazione (o, più precisamente, i generatori)? A differenza della returnparola chiave misero , yieldè in grado di passare valori al suo chiamante e ricevere valori dal suo chiamante tutto senza terminare la funzione in cui vive! (Naturalmente, se si desidera terminare una funzione - o un generatore - è utile avere anche la returnparola chiave.) Quando yieldviene rilevata un'istruzione, la funzione generatore si interrompe e riprende da dove era rimasta spento dopo l'invio di un altro valore. Ed sendè solo l'interfaccia per comunicare con l'interno di una funzione del generatore dall'esterno.

Se vogliamo davvero rompere al massimo questa analogia push / pull / pipe, finiamo con il seguente pseudocodice che porta davvero a casa che, a parte i passaggi 1-5, yielde sendsono due facce dello stesso gettone :

  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

La trasformazione chiave è che abbiamo diviso x = yield ye value1 = g.send(value2)ciascuno in due affermazioni: left_end.push(y)e x = left_end.pull(); e value1 = right_end.pull()e right_end.push(value2). Esistono due casi speciali della yieldparola chiave: x = yielde yield y. Questi sono zuccheri sintattici, rispettivamente, per x = yield Nonee _ = yield y # discarding value.

Per dettagli specifici sull'ordine preciso in cui i valori vengono inviati attraverso la pipe, vedere di seguito.


Quello che segue è un modello concreto piuttosto lungo di quanto sopra. In primo luogo, occorre innanzitutto rilevare che per ogni generatore g, next(g)è esattamente equivalente g.send(None). Con questo in mente possiamo concentrarci solo su come sendfunziona e parlare solo dell'avanzamento del generatore send.

Supponiamo di avere

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

Ora, la definizione di fcirca desugar alla seguente funzione ordinaria (non generatrice):

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

In questa trasformazione di f:

  1. Abbiamo spostato l'implementazione in una funzione nidificata.
  2. Abbiamo creato una pipe bidirezionale a cui left_endaccederà la funzione nidificata e che right_endverrà restituita e accessibile dall'ambito esterno - right_endè ciò che conosciamo come oggetto generatore.
  3. All'interno della funzione nidificata, la prima cosa che facciamo è controllare che left_end.pull()è None, consumando un valore spinto nel processo.
  4. All'interno della funzione nidificata, l'istruzione x = yield yè stata sostituita da due righe: left_end.push(y)e x = left_end.pull().
  5. Abbiamo definito la sendfunzione per right_end, che è la controparte delle due righe con cui abbiamo sostituito l' x = yield yistruzione nel passaggio precedente.

In questo mondo fantastico in cui le funzioni possono continuare dopo il ritorno, gviene assegnato right_ende quindi impl()chiamato. Quindi, nel nostro esempio sopra, se seguissimo l'esecuzione riga per riga, ciò che accadrebbe sarebbe all'incirca il seguente:

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

Questo si associa esattamente allo pseudocodice di 16 passaggi sopra.

Ci sono alcuni altri dettagli, come la modalità di propagazione degli errori e cosa succede quando si raggiunge la fine del generatore (il tubo è chiuso), ma ciò dovrebbe chiarire come funziona il flusso di controllo di base quando sendviene utilizzato.

Usando queste stesse regole di desugaring, diamo un'occhiata a due casi speciali:

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

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

Per la maggior parte desugar allo stesso modo f, le uniche differenze sono le modalità di yieldtrasformazione delle affermazioni:

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

Nel primo, il valore passato a f1viene inizialmente spinto (ceduto), quindi tutti i valori estratti (inviati) vengono spinti (ceduti) all'indietro. Nel secondo, xnon ha valore (ancora) quando arriva per la prima volta push, quindi UnboundLocalErrorviene sollevato.


"L'argomento 1 in g = f (1) è stato catturato normalmente e assegnato a y all'interno del corpo di f, ma il vero True non è ancora iniziato." Perchè no? Perché Python non dovrebbe provare a eseguire questo codice fino a quando non incontra ad esempio yield?
Josh,

@Josh Il cursore non è avanzato fino alla prima chiamata a send; serve una chiamata di send(None)per spostare il cursore sulla prima yieldistruzione e solo allora le sendchiamate successive inviano effettivamente un valore "reale" a yield.
BallpointBen

Grazie - Questo è interessante, quindi l'interprete sa che la funzione di f volontà yield ad un certo punto, e quindi attendere che si ottiene una senddal chiamante? Con una normale funzione cal, l'interprete avrebbe appena iniziato a eseguire fsubito, giusto? Dopotutto, non esiste una compilazione AOT di alcun tipo in Python. Sei sicuro che sia così? (senza mettere in discussione ciò che stai dicendo, sono davvero solo perplesso da ciò che hai scritto qui). Dove posso leggere di più su come Python sa che deve attendere prima che inizi a eseguire il resto della funzione?
Josh,

@Josh Ho costruito questo modello mentale solo osservando come funzionano diversi generatori di giocattoli, senza alcuna comprensione degli interni di Python. Tuttavia, il fatto che l'iniziale send(None)fornisca il valore appropriato (ad es. 1) Senza inviare Nonenel generatore suggerisce che la prima chiamata a sendè un caso speciale. È un'interfaccia complicata da progettare; se si lascia che il primo sendinvii un valore arbitrario, l'ordine dei valori restituiti e dei valori inviati sarebbe spento di uno rispetto a quello che è attualmente.
BallpointBen

Grazie BallpointBen. Molto interessante, ho lasciato una domanda qui per vedere perché è così.
Josh,

2

Anche questi mi hanno confuso. Ecco un esempio che ho fatto quando ho cercato di installare un generatore che produce e accetta segnali in ordine alternato (resa, accetta, rendi, accetta) ...

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)

L'output è:

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!"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.