2013-05-05 1 views
6

Questa domanda mi sta facendo tirare fuori i capelli.In che modo i generatori Python sanno chi sta chiamando?

se faccio:

def mygen(): 
    for i in range(100): 
     yield i 

e chiamare da mille discussioni, come fa il generatore sa cosa inviare seguente per ogni thread? Ogni volta che lo chiamo, il generatore salva una tabella con il contatore e il riferimento del chiamante o qualcosa del genere?

È strano.

Per favore, chiarisci la mia mente su quello.

risposta

6

mygen non deve ricordare nulla. Ogni chiamata a mygen() restituisce un iterabile indipendente. Questi iterables, d'altra parte, hanno lo stato: ogni volta che viene richiamato lo next(), si passa alla posizione corretta nel codice del generatore: quando viene rilevato un numero yield, il controllo viene restituito al chiamante. L'implementazione effettiva è piuttosto caotica, ma in linea di principio è possibile immaginare che un tale iteratore memorizzi le variabili locali, il bytecode e la posizione corrente nel bytecode (puntatore dell'istruzione a.k.a.). Non c'è niente di speciale nei thread qui.

+0

Sì, i thread erano solo per illustrare il problema. Considerando che i generatori potrebbero dare l'aspetto sbagliato della concorrenza (o qualcosa di più magico-nero di quello) ai principianti di pitone. –

+0

@PatrickBassut: Beh, puoi simulare [coroutine] (https://en.wikipedia.org/wiki/Coroutine) con loro, e con le coroutine puoi creare [discussioni verdi] (https://en.wikipedia.org/wiki/Green_threads). I generatori – icktoofay

2

Una funzione come questa, quando chiamata, restituirà un oggetto generatore. Se si dispone di thread separati che chiamano next() sullo stesso oggetto generatore, interferiranno reciprocamente. Vale a dire, 5 thread chiamando next() 10 volte ciascuno otterrà 50 rendimenti diversi.

Se due thread creano un generatore chiamando lo mygen() all'interno del thread, avranno oggetti generatore separati.

Un generatore è un oggetto e il suo stato verrà archiviato in memoria, quindi due thread che creano uno mygen() si riferiranno a oggetti separati. Non sarebbe diverso da due thread che creano un oggetto da un class, ognuno avrà un oggetto diverso, anche se la classe è la stessa.

se si proviene da uno sfondo C, questo è non la stessa cosa di una funzione con le variabili static. Lo stato viene mantenuto in un oggetto, non staticamente nelle variabili contenute nella funzione.

+0

Non hai risposto alla mia domanda. Non voglio sapere cosa succederà, ma come succede. Il generatore mantiene un certo valore in memoria quando un thread crea un generatore? –

+4

@PatrickBassut un oggetto generatore ha uno stato proprio come qualsiasi altro oggetto, quindi sì, il suo stato sarà memorizzato. –

1

Potrebbe essere più chiaro se si guarda in questo modo. Invece di:

for i in mygen(): 
    . . . 

uso:

gen_obj = mygen() 
for i in gen_obj: 
    . . . 

allora si può vedere che mygen() viene chiamato solo una volta, ed è crea un nuovo oggetto, ed è l'oggetto che viene iterato. È possibile creare due sequenze nello stesso thread, se si voleva:

gen1 = mygen() 
gen2 = mygen() 
print(gen1.__next__(), gen2.__next__(), gen1.__next__(), gen2.__next__()) 

questo stampa 0, 0, 1, 1.

Si potrebbe accedere allo stesso iteratore da due fili, se volete, basta memorizzare l'oggetto generatore in un globale:

global_gen = mygen() 

filettatura 1:

for i in global_gen: 
    . . . 

filettatura 2:

for i in global_gen: 
    . . . 

Questo probabilmente causerebbe tutti i tipi di scempio. :-)

+0

sono piuttosto strani. Puoi assegnarli alle variabili, ma al momento in cui li usi effettivamente inizia a generare valori in una sorta di "su richiesta". Se hai assegnato la posizione di memoria di una funzione, sarebbe stato di facile comprensione. Ma per quanto ne so, non lo fai lì (in realtà stai chiamando la funzione in gen_obj = mygen()). Wow! –

+1

Quando si passa al livello successivo di Python-Fu serio, è possibile passare i puntatori ai metodi __next __() associati come callback GUI. :-) –

+1

La maggior parte delle volte, l'istruzione "def" crea un singolo oggetto funzione dal codice, che viene eseguito quando si chiama la funzione per nome. Se il codice contiene "yield", tuttavia, def crea * due * oggetti funzione: quello che viene chiamato quando invochi il nome della funzione non ha affatto il tuo codice; restituisce solo l'oggetto generatore. L'oggetto funzione creato dal codice viene chiamato tramite l'attributo __next__ di tale oggetto generatore e tale funzione mantiene il suo stato nell'oggetto generatore e sa come salvarlo e ripristinarlo tra le chiamate. –