yield verhält sich merkwürdig.

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
Max77
User
Beiträge: 19
Registriert: Samstag 30. April 2016, 13:42

[codebox=text file=Unbenannt.txt]Ich habe eine Frage zu yield. Was macht diese Anweisung ? Wenn ich folgendes tippe:


def quadrieren():

variable = 1

while True:

yield variable * variable

variable += 1



print quadrieren().next()
print quadrieren().next()
print quadrieren().next()


AUSGABE:
>>>
1
1
1


Wenn ich stattdessen tippe:


a = quadrieren()

print a.next()
print a.next()
print a.next()

AUSGABE:
>>>
1
4
9

Wieso kommt es zu so unterschiedlichen AUSGABEN ?

Es müßte doch egal bzw. das Gleiche bedeuten ob ich nun gleich

print quadrieren().next()

schreibe oder erst

a = quadrieren()

und dann

print a.next()

DAS verstehe ich nicht !











[/code]
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Jedesmal wenn du eine Funktion in der ein yield auftaucht aufrufst erzeugst du einen neuen Generator. Dieser Generator merkt sich wo er gerade ist und welche Werte die lokalen Variablen haben und mit jedem .next() geht der Generator weiter bis zum nächsten yield.

Code: Alles auswählen

quadrieren().next()
quadrieren().next()
Erzeugt einen Generator, führt diesen bis zum yield aus und gibt dann zurück was auch immer yield produziert hat. In der zweiten Zeile passiert dass dann nochmal.

Code: Alles auswählen

a = quadrieren()
a.next()
a.next()
Erzeugt einen Generator a. a.next() führt den Generator aus und der läuft dann bis zum yield und liefert das Ergebnis. a bleibt an diesem yield stehen. Wenn wir dann, wie hier in der dritten Zeile, a.next() nochmal aufrufen läuft der Generator weiter, von dem yield aus an dem er zuvor aufgehört hat.
Max77
User
Beiträge: 19
Registriert: Samstag 30. April 2016, 13:42

Also erzeuge ich mit drei mal print quadrieren().next() auch DREI GENERATOREN
während bei a=quadrieren und dreimal print a.next nur EIN GENERATOR erzeugt wird ?
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Ja.
Benutzeravatar
/me
User
Beiträge: 3554
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Max77 hat geschrieben:Also erzeuge ich mit drei mal print quadrieren().next() auch DREI GENERATOREN
während bei a=quadrieren und dreimal print a.next nur EIN GENERATOR erzeugt wird ?
Ja. Der Generator wird durch den Aufruf von quadrieren() erzeugt und diesen Aufruf hast du bei deinem ersten Beispiel drei Mal. Das next() erzeugt keinen Generator, das ist einfach eine Methode eines bereits bestehenden Generators.
BlackJack

Kleine Anmerkung zum `next()`: Es gibt auch eine `next()`-Funktion die man der Methode vorziehen sollte. In Python 3 gibt es nämlich nur noch die Funktion aber nicht mehr die Methode. Man spart sich damit also zukünftige Arbeit beim Portieren.

Code: Alles auswählen

next(quadrieren())
next(quadrieren())

a = quadrieren()
next(a)
next(a)
Edit: Und um noch mal den Punkt mit dem Sinn von Debuggern aus dem anderen Thema aufzugreifen: `quadrieren()` würde eher so auseehen:

Code: Alles auswählen

from itertools import count

def quadrieren():
    return (v * v for v in count(1))
Also nur ein einziger (Generator)Ausdruck und da kann man schlecht einen Einzelschritt-Debugger ansetzen.
BlackJack

@Max77: Hier mal die Entwicklung von so einem Quadratzahlen-Iterator über die Zeit mit jeweils hinzugekommenen Spracheigenschaften:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function
from itertools import count


class Quadratzahlen(object):

    def __init__(self):
        self.variable = 0

    def __iter__(self):
        return self

    def next(self):
        self.variable += 1
        return self.variable * self.variable


def quadratzahlen_a():
    variable = 0
    while True:
        variable += 1
        yield variable * variable


def quadratzahlen_b():
    for variable in count(1):
        yield variable * variable


def quadratzahlen_c():
    return (variable * variable for variable in count(1))


def main():
    funcs = [Quadratzahlen, quadratzahlen_a, quadratzahlen_b, quadratzahlen_c]

    for func in funcs:
        print(func)

        for _ in xrange(3):
            print(next(func()))
        print('-' * 10)
        iterator = func()
        for _ in xrange(3):
            print(next(iterator))

        print()

    iterators = [f() for f in funcs]
    for _ in xrange(1000000):
        values = [next(it) for it in iterators]
        assert all(values[0] == v for v in values)


if __name__ == '__main__':
    main()
Am Anfang musste man für solche Iteratoren noch eine eigene Klasse schreiben (`Quadratzahlen`), die das Iterator-Protokoll implementiert. Also eine `__iter__()`-Methode, die das Exemplar selbst liefert, und eine `next()`-Methode die entweder das nächste Element liefert oder eine `StopIteration`-Ausnahme auslöst. Da das ein Endlositerator ist — also zumindest solange man genug Speicher für die Zahlen hat ;-) — fällt die Ausnahme hier weg.

Dann kam das Schlüsselwort ``yield`` mit dem man eine Generatorfunktion (`quadratzahlen_a()`) schreiben kann, die das gleiche macht, aber weniger Quelltext erfordert.

Das manuelle hochzählen von `variable` kann man sich sparen wenn man dafür `itertools.count()` verwendet, das einen Endlos-Iterator über fortlaufende Zahlen liefert: `quadratzahlen_b()`.

Durch die Auführung von Generatorausdrücken kann man das ganze dann noch ein klein wenig vereinfachen und kommt somit vom Ursprung, einer Klasse mit drei Methoden, in `quadratzahlen_c()` zu einer normalen Funktion mit einem Rückgabewert der aus einem einzigen Ausdruck besteht.

In der `main()` zeige ich, dass die vier Varianten alle das gleiche ”komische” Verhalten zeigen weswegen Du dieses Thema aufgemacht hast, und das alle vier Iteratoren zumindest bei der ersten Million Werte das gleiche liefern.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Fehlt noch, dass man einen Iterator meist in einer for-Schleife verwendet und so das manuelle next wegfällt, so dass aus

Code: Alles auswählen

        iterator = func()
        for _ in xrange(3):
            print(next(iterator))
das hier wird:

Code: Alles auswählen

        for value in islice(func(), 3):
            print(value)
Antworten