2013-05-05 9 views
6

Diese Frage bringt mich dazu, meine Haare herauszuziehen.Wie Python-Generatoren wissen, wer anruft?

, wenn ich tun:

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

und nennen es von einem tausend Fäden, wie funktioniert weiß der Generator, was für jeden Thread als nächstes senden? Jedes Mal wenn ich es nenne, speichert der Generator eine Tabelle mit dem Zähler und der Anruferreferenz oder etwas in der Art?

Es ist komisch.

Bitte, klar meine Meinung zu diesem.

Antwort

6

mygen muss sich an nichts erinnern. Jeder Aufruf an mygen() gibt ein unabhängiges iterables zurück. Diese Iterables haben dagegen den Status: Jedes Mal, wenn next() an einem aufgerufen wird, springt es an die richtige Stelle im Generatorcode - wenn eine yield auftritt, wird die Steuerung an den Aufrufer zurückgegeben. Die tatsächliche Implementierung ist ziemlich unordentlich, aber im Prinzip kann man sich vorstellen, dass ein solcher Iterator die lokalen Variablen, den Bytecode und die aktuelle Position im Bytecode speichert (a.k.a. Befehlszeiger). Thread ist hier nichts besonderes.

+0

Ja, die Threads waren nur um das Problem zu veranschaulichen. Bedenkt man, dass Generatoren den Python-Anfängern das falsche Aussehen von Nebenläufigkeit (oder etwas mehr Schwarzmagie als das) geben könnten. –

+0

@PatrickBassut: Nun, Sie können [coroutines] (https://en.wikipedia.org/wiki/Coroutine) mit ihnen simulieren, und mit Korotinen können Sie [grüne Fäden] machen (https://en.wikipedia.org/wiki/Green_threads). – icktoofay

2

Eine solche Funktion wird, wenn sie aufgerufen wird, ein Generatorobjekt zurückgeben. Wenn Sie separate Threads aufrufen, die next() auf dem gleichen Generatorobjekt aufrufen, werden sie sich gegenseitig stören. Das heißt, 5 Threads, die next() 10 mal aufrufen, erhalten 50 verschiedene Erträge.

Wenn zwei Threads jeweils einen Generator erstellen, indem sie innerhalb des Threads mygen() aufrufen, haben sie separate Generatorobjekte.

Ein Generator ist ein Objekt, und sein Zustand wird im Speicher gespeichert, so dass zwei Threads, die jeweils eine mygen() erstellen, auf separate Objekte verweisen. Es wäre nicht anders als zwei Threads, die ein Objekt aus einer class erstellen, sie haben jeweils ein anderes Objekt, obwohl die Klasse die gleiche ist.

Wenn Sie von einem C-Hintergrund kommen, ist dies nicht das gleiche wie eine Funktion mit static Variablen. Der Zustand wird in einem Objekt, nicht statisch in den in der Funktion enthaltenen Variablen gepflegt.

+0

Sie haben meine Frage nicht ganz beantwortet. Ich will nicht wissen, was passieren wird, aber wie es passiert. Behält der Generator einen gewissen Wert im Speicher, wenn ein Thread einen Generator erzeugt? –

+4

@PatrickBassut ein Generator Objekt hat einen Zustand wie jedes andere Objekt, also ja, sein Zustand wird im Speicher gespeichert. –

1

Es könnte klarer sein, wenn Sie es auf diese Weise betrachten. Statt:

for i in mygen(): 
    . . . 

Verwendung:

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

dann können Sie diese mygen() sehen nur einmal aufgerufen wird, und es schafft ein neues Objekt, und es ist das Objekt, das wiederholt wird. Sie können zwei Sequenzen im selben Thread erstellen, wenn man will:

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

Dies wird 0, 0, 1, 1 drucken.

Sie könnten das gleiche Iterator von zwei Threads zugreifen, wenn Sie möchten, speichern Sie einfach den Generator Objekt in einem global:

global_gen = mygen() 

Gewinde 1:

for i in global_gen: 
    . . . 

Thema 2:

for i in global_gen: 
    . . . 

Dies würde wahrscheinlich alle Arten von Chaos verursachen. :-)

+0

Generatoren sind irgendwie komisch. Sie können sie Variablen zuweisen, aber in dem Moment, in dem Sie sie tatsächlich verwenden, beginnt sie, Werte auf eine Art "on demand" zu erzeugen. Wenn Sie den Speicherort einer Funktion zugewiesen haben, ist das ganz einfach zu verstehen. Aber soweit ich weiß, tun Sie das nicht (Sie rufen die Funktion in gen_obj = mygen() auf). Beeindruckend! –

+1

Wenn Sie zur nächsten Ebene von ernstem Python-Fu übergehen, können Sie Zeiger auf gebundene __next __() -Methoden als GUI-Callbacks übergeben. :-) –

+1

Meistens erstellt die "def" -Anweisung ein einzelnes Funktionsobjekt aus Ihrem Code, das ausgeführt wird, wenn Sie die Funktion namentlich aufrufen. Wenn der Code "yield" enthält, erzeugt das def jedoch * two * function-Objekte: dasjenige, das beim Aufruf des Funktionsnamens aufgerufen wird, hat überhaupt keinen Code; Es gibt nur das Generatorobjekt zurück. Das Funktionsobjekt, das es aus Ihrem Code erstellt, wird über das Attribut __next__ dieses Generatorobjekts aufgerufen, und diese Funktion behält ihren Status im Generatorobjekt bei und weiß, wie sie zwischen den Aufrufen gespeichert und wiederhergestellt wird. –

Verwandte Themen