[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]
yield verhält sich merkwürdig.
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.
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.
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.
Code: Alles auswählen
quadrieren().next()
quadrieren().next()
Code: Alles auswählen
a = quadrieren()
a.next()
a.next()
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.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 ?
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.
Edit: Und um noch mal den Punkt mit dem Sinn von Debuggern aus dem anderen Thema aufzugreifen: `quadrieren()` würde eher so auseehen:
Also nur ein einziger (Generator)Ausdruck und da kann man schlecht einen Einzelschritt-Debugger ansetzen.
Code: Alles auswählen
next(quadrieren())
next(quadrieren())
a = quadrieren()
next(a)
next(a)
Code: Alles auswählen
from itertools import count
def quadrieren():
return (v * v for v in count(1))
@Max77: Hier mal die Entwicklung von so einem Quadratzahlen-Iterator über die Zeit mit jeweils hinzugekommenen Spracheigenschaften:
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.
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()
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.
Fehlt noch, dass man einen Iterator meist in einer for-Schleife verwendet und so das manuelle next wegfällt, so dass aus
das hier wird:
Code: Alles auswählen
iterator = func()
for _ in xrange(3):
print(next(iterator))
Code: Alles auswählen
for value in islice(func(), 3):
print(value)