Funktionsweise von Generatoren unklar...

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Folgendes verstehe ich nicht:

Code: Alles auswählen

def gen(value):
    while True:
        value = yield value

Code: Alles auswählen

In [8]: g = gen('foo')

In [9]: print g.send(None)
foo

In [10]: print g.send('bar')
bar

In [11]: print g.next()
None
Weshalb wird `value` auf `None` gesetzt oder aber nicht `value`, sondern `None` zurückgegeben?
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Nun erstmal hilft es zu schauen was genau passiert.

Code: Alles auswählen

>>> def gen(value):
...     while True:
...             print('pre-yield', value)
...             value = yield value
...             print('post-yield', value)
... 
>>> g = gen('foo')
>>> g.send(None)
pre-yield foo
'foo'
>>> g.send('bar')
post-yield bar
pre-yield bar
'bar'
>>> next(g)
post-yield None
pre-yield None
`value` wird auf `None` gesetzt. Irgendwie sowas muss auch passieren weil `value` zu irgendwas gesetzt werden muss du aber nichts an den generator schickst. Das Verhalten ist so übrigens auch dokumentiert.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Ok, das ist mir nun soweit klar. Nur: Wo steht der Generator, nachdem er mit `send` oder `next` angestupst wurde?

Code: Alles auswählen

#
               1   3       2
               |   |       |
def gen(value):|   |       |
    while True:____|       |
        value =|yield value|

1: g = gen(1)
2: g.send(None)
3: g.send(2)
Wo findet die Zuweisung zu `value` statt? Beziehungsweise, hat `value` nach seiner Zuweisung und der Rückgabe noch denselben Wert und wird erst dann auf `None` gesetzt, wenn der folgende Stupser keinen Wert mitbringt?
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
nezzcarth
User
Beiträge: 1633
Registriert: Samstag 16. April 2011, 12:47

Möglicherweise verstehe ich deine Frage nicht richtig, aber meiner Meinung nach beschreibst du den Sachverhalt doch schon so, wie ich ihn auch verstanden hatte: Für mich ist 'yield' im Prinzip ein Statement zum Pausieren, das beim Eintritt in den Pausezustand etwas an den Aufrufer zurück liefern kann (optional, aber üblich) und bei der Rückkehr aus der Pause einen Wert in die aufgerufene Generatorfunktion zurück mitbringen kann (optional und seltener); oder anders formuliert gewissermaßen ein "Zwei-Wege-return", das erst nach außen und dann nach innen funktioniert.

(Das Pausier-Verhalten nutzt man ja auch in der zunächst etwas seltsam anmutenden Verwendung zur Definition von Context-Managern mit einem leeren 'yield' aus. Der Teil bis einschließlich rechts von 'yield' entspricht dem '__enter__', alles links und unterhalb von 'yield' entspricht '__exit__' und in der Pause wird der eigentliche Code ausgeführt)
mutetella hat geschrieben:Wo findet die Zuweisung zu `value` statt?
Beziehungsweise, hat `value` nach seiner Zuweisung und der Rückgabe noch denselben Wert und wird erst dann auf `None` gesetzt, wenn der folgende Stupser keinen Wert mitbringt?
Graphisch gesprochen wertet der Generator das Statement rechts von 'yield' aus, gibt es an den Aufrufer zurück und pausiert dann quasi rechts vor dem "=". Nach dem "Wiederanstupsen" steht da dann im Prinzip "value = None", wenn das Anstupsen mit next() erfolgt resp. value = <x im Ausdruck 'gen.send(x)'>, wenn man 'send' verwendet. Dann wird der Code wieder bis zum Gleichheitszeichen ausgeführt. So erkläre ich mir das zumindest.
BlackJack

@mutetella: Nach einem `send()` oder `next()` wird die Zuweisung an `value` ausgeführt. Weil erst dann steht ja fest welcher Wert dort zugewiesen werden muss, das ist vor einem `send()` oder `next()` ja unbekannt. Oder in Bytecode ausgedrückt:

Code: Alles auswählen

In [1]: def gen(value):
   ...:     while True:
   ...:         value = yield value
   ...: 

In [2]: import dis

In [3]: dis.dis(gen)
  2           0 SETUP_LOOP              17 (to 20)
        >>    3 LOAD_GLOBAL              0 (True)
              6 POP_JUMP_IF_FALSE       19

  3           9 LOAD_FAST                0 (value)
             12 YIELD_VALUE         
             13 STORE_FAST               0 (value)
             16 JUMP_ABSOLUTE            3
        >>   19 POP_BLOCK           
        >>   20 LOAD_CONST               0 (None)
             23 RETURN_VALUE
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Vielen Dank für das Licht in meinem dunklen Kopf... :D

Zum Verstehen des bytecodes fehlt mir unter anderem noch eine Vorstellung, was dieser Top Of Stack eigentlich genau ist? Einmal heißt es
LOAD_GLOBAL(namei)
Loads the global named co_names[namei] onto the stack.

LOAD_FAST(var_num)
Pushes a reference to the local co_varnames[var_num] onto the stack.
was für mich so aussieht, als ob TOS sowas wie eine Liste von Konstanten, Namen und Referenzen ist, dann steht in der Doku aber wieder
YIELD_VALUE()
Pops TOS and yields it from a generator.

STORE_FAST(var_num)
Stores TOS into the local co_varnames[var_num].
was ja danach aussieht, als ob hier nicht einzelne Elemente aus dem TOS, sondern der gesamte TOS betroffen ist. Also `YIELD_VALUE()` beispielsweise gibt den gesamten TOS zurück, was ja nicht sein kann, wenn dieser TOS eine Liste, so wie in meiner Vorstellung, wäre.
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Nunja, der Stack ist ein Stack (oder Kellerspeicher) ;) Und so ein Stack hat genau eine zugaengliche Position: Die Spitze (oder ToS).
Das ist also genau ein Wert der da steht. Was genau da steht, haengt ab von den umgebenden Operationen aber das ist im Grunde alles, von Referenzen ueber Werte zu Addressen.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@mutetella
Bitte nicht die Begriffe vermischen. "stack", welcher in deinem ersten Zitat der Doku angesprochen wird, ist die Gesamtheit aller abgelegten (wie auch immer gearteten) Objekte. Also der komplette Stapel.

TOS (Top of Stack) ist das zuletzt abgelegte Element. Dieses oberste Element ist bei Stacks das einzige Element, das man wieder herunter nehmen kann. Wenn das TOS herunter genommen wurde, dann wird anschließend das zuvor zweitoberste Element (falls vorhanden) zum obersten Element und damit zum TOS.

Bei deinem ersten Zitat wird also jeweils etwas auf dem Stack abgelegt. Bei deinem zweiten Zitat ist jeweils das oberste Element des Stacks gemeint.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@snafu
Das hatte ich tatsächlich nicht getrennt! Jetzt wird auch ein Schuh daraus!
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Antworten