Generatoren in Python verstehen

Lesezeit: 3 Minuten

Benutzer-Avatar
Federer

Ich lese gerade das Python-Kochbuch und schaue mir gerade Generatoren an. Es fällt mir schwer, meinen Kopf zu drehen.

Da ich einen Java-Hintergrund habe, gibt es ein Java-Äquivalent? Das Buch sprach von ‘Producer / Consumer’, aber wenn ich das höre, denke ich an Threading.

Was ist ein Generator und warum würden Sie ihn verwenden? Natürlich ohne irgendwelche Bücher zu zitieren (es sei denn, Sie können eine anständige, vereinfachte Antwort direkt aus einem Buch finden). Vielleicht mit Beispielen, wenn Sie sich großzügig fühlen!

Benutzer-Avatar
Stefan202

Hinweis: Dieser Beitrag geht von Python 3.x-Syntax aus.

EIN Generator ist einfach eine Funktion, die ein Objekt zurückgibt, das Sie aufrufen können nextso dass es für jeden Aufruf einen Wert zurückgibt, bis es a auslöst StopIteration Ausnahme, die signalisiert, dass alle Werte generiert wurden. Ein solches Objekt wird als ein bezeichnet Iterator.

Normale Funktionen geben einen einzelnen Wert mit zurück return, genau wie in Java. In Python gibt es jedoch eine Alternative namens yield. Verwenden yield irgendwo in einer Funktion macht es zu einem Generator. Beachten Sie diesen Code:

>>> def myGen(n):
...     yield n
...     yield n + 1
... 
>>> g = myGen(6)
>>> next(g)
6
>>> next(g)
7
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Wie du sehen kannst, myGen(n) ist eine Funktion, die nachgibt n und n + 1. Jeder Anruf bei next ergibt einen einzelnen Wert, bis alle Werte geliefert wurden. for Schleifen rufen next im Hintergrund, also:

>>> for n in myGen(6):
...     print(n)
... 
6
7

Ebenso gibt es Generatorausdrückedie eine Möglichkeit bieten, bestimmte gängige Arten von Generatoren kurz zu beschreiben:

>>> g = (n for n in range(3, 5))
>>> next(g)
3
>>> next(g)
4
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Beachten Sie, dass Generatorausdrücke sehr ähnlich sind Verständnis auflisten:

>>> lc = [n for n in range(3, 5)]
>>> lc
[3, 4]

Beachten Sie, dass ein Generatorobjekt generiert wird einmalaber sein Code ist nicht auf einmal laufen. Ruft nur an next Ausführen (eines Teils) des Codes. Die Ausführung des Codes in einem Generator stoppt einmal a yield -Anweisung erreicht wurde, woraufhin sie einen Wert zurückgibt. Der nächste Anruf bei next bewirkt dann, dass die Ausführung in dem Zustand fortgesetzt wird, in dem der Generator nach dem letzten verlassen wurde yield. Dies ist ein grundlegender Unterschied zu regulären Funktionen: Diese beginnen die Ausführung immer “oben” und verwerfen ihren Zustand, wenn sie einen Wert zurückgeben.

Zu diesem Thema gibt es noch mehr zu sagen. Es ist zB möglich send Daten zurück in einen Generator (Hinweis). Aber ich schlage vor, dass Sie sich damit nicht befassen, bis Sie das Grundkonzept eines Generators verstanden haben.

Jetzt fragen Sie sich vielleicht: Warum Generatoren verwenden? Es gibt ein paar gute Gründe:

  • Bestimmte Konzepte lassen sich mit Generatoren viel prägnanter beschreiben.
  • Anstatt eine Funktion zu erstellen, die eine Liste von Werten zurückgibt, kann man einen Generator schreiben, der die Werte im laufenden Betrieb generiert. Das bedeutet, dass keine Liste erstellt werden muss, was bedeutet, dass der resultierende Code speichereffizienter ist. Auf diese Weise kann man sogar Datenströme beschreiben, die einfach zu groß wären, um in den Speicher zu passen.
  • Generatoren ermöglichen eine natürliche Art der Beschreibung unendlich Ströme. Betrachten Sie zum Beispiel die Fibonacci-Zahlen:

    >>> def fib():
    ...     a, b = 0, 1
    ...     while True:
    ...         yield a
    ...         a, b = b, a + b
    ... 
    >>> import itertools
    >>> list(itertools.islice(fib(), 10))
    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
    

    Dieser Code verwendet itertools.islice eine endliche Anzahl von Elementen aus einem unendlichen Strom zu nehmen. Es wird empfohlen, sich die Funktionen in der gut anzusehen itertools Modul, da sie wesentliche Werkzeuge zum einfachen Schreiben fortschrittlicher Generatoren sind.


Über Python <=2.6: in den obigen Beispielen next ist eine Funktion, die die Methode aufruft __next__ auf dem gegebenen Objekt. In Python <=2.6 verwendet man eine etwas andere Technik, nämlich o.next() Anstatt von next(o). Python 2.7 hat next() Anruf .next Sie müssen also Folgendes in 2.7 nicht verwenden:

>>> g = (n for n in range(3, 5))
>>> g.next()
3

  • Sie erwähnen, dass es möglich ist send Daten an einen Generator. Sobald Sie das tun, haben Sie eine „Koroutine“. Es ist sehr einfach, Muster wie den erwähnten Consumer/Producer mit Coroutinen zu implementieren, da sie keine Notwendigkeit dafür haben Locks und kann daher nicht Deadlock. Es ist schwer, Coroutinen zu beschreiben, ohne Threads zu zerschlagen, also sage ich einfach, Coroutinen sind eine sehr elegante Alternative zum Threading.

    – Jochen Ritzel

    18. November 2009 um 14:47 Uhr

  • Sind Python-Generatoren in Bezug auf ihre Funktionsweise im Wesentlichen Turing-Maschinen?

    – Feuriger Phönix

    23. September 2016 um 23:34 Uhr

  • Fibonacci mit yield

    – Milovan Tomašević

    9. Januar 2021 um 22:29 Uhr

Benutzer-Avatar
Kaleb Hattingh

Ein Generator ist effektiv eine Funktion, die (Daten) zurückgibt, bevor sie beendet ist, aber an diesem Punkt pausiert, und Sie können die Funktion an diesem Punkt fortsetzen.

>>> def myGenerator():
...     yield 'These'
...     yield 'words'
...     yield 'come'
...     yield 'one'
...     yield 'at'
...     yield 'a'
...     yield 'time'

>>> myGeneratorInstance = myGenerator()
>>> next(myGeneratorInstance)
These
>>> next(myGeneratorInstance)
words

usw. Der (oder ein) Vorteil von Generatoren besteht darin, dass Sie mit großen Datenmengen umgehen können, da sie die Daten Stück für Stück verarbeiten. bei Listen könnte ein zu hoher Speicherbedarf zum Problem werden. Generatoren sind genau wie Listen iterierbar, sodass sie auf die gleiche Weise verwendet werden können:

>>> for word in myGeneratorInstance:
...     print word
These
words
come
one
at 
a 
time

Beachten Sie, dass Generatoren beispielsweise eine andere Möglichkeit bieten, mit der Unendlichkeit umzugehen

>>> from time import gmtime, strftime
>>> def myGen():
...     while True:
...         yield strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())    
>>> myGeneratorInstance = myGen()
>>> next(myGeneratorInstance)
Thu, 28 Jun 2001 14:17:15 +0000
>>> next(myGeneratorInstance)
Thu, 28 Jun 2001 14:18:02 +0000   

Der Generator kapselt eine Endlosschleife, aber das ist kein Problem, weil Sie jede Antwort nur jedes Mal bekommen, wenn Sie danach fragen.

Benutzer-Avatar
Nikow

Zunächst einmal der Begriff Generator war ursprünglich in Python etwas schlecht definiert, was zu viel Verwirrung führte. Du meinst wahrscheinlich Iteratoren und Iterables (sehen hier). Dann gibt es in Python auch Generatorfunktionen (die ein Generatorobjekt zurückgeben), Generator-Objekte (die Iteratoren sind) und Generatorausdrücke (die zu einem Generatorobjekt ausgewertet werden).

Entsprechend der Glossareintrag für Generator es scheint, dass die offizielle Terminologie jetzt das ist Generator ist die Abkürzung für „Generatorfunktion“. In der Vergangenheit hat die Dokumentation die Begriffe uneinheitlich definiert, aber das wurde glücklicherweise behoben.

Es könnte dennoch eine gute Idee sein, genau zu sein und den Begriff “Generator” ohne weitere Spezifizierung zu vermeiden.

  • Hmm, ich denke, Sie haben recht, zumindest nach einem Test einiger Zeilen in Python 2.6. Ein Generatorausdruck gibt einen Iterator (auch als „Generatorobjekt“ bezeichnet) zurück, keinen Generator.

    – Craig McQueen

    4. Dezember 2009 um 1:34 Uhr

Benutzer-Avatar
überdenken

Generatoren könnte man sich als Kürzel zum Erstellen eines Iterators vorstellen. Sie verhalten sich wie ein Java-Iterator. Beispiel:

>>> g = (x for x in range(10))
>>> g
<generator object <genexpr> at 0x7fac1c1e6aa0>
>>> g.next()
0
>>> g.next()
1
>>> g.next()
2
>>> list(g)   # force iterating the rest
[3, 4, 5, 6, 7, 8, 9]
>>> g.next()  # iterator is at the end; calling next again will throw
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Hoffe, das hilft / ist das, wonach Sie suchen.

Aktualisieren:

Wie viele andere Antworten zeigen, gibt es verschiedene Möglichkeiten, einen Generator zu erstellen. Sie können die Klammersyntax wie in meinem obigen Beispiel verwenden, oder Sie können yield verwenden. Ein weiteres interessantes Merkmal ist, dass Generatoren „unendlich“ sein können – Iteratoren, die nicht aufhören:

>>> def infinite_gen():
...     n = 0
...     while True:
...         yield n
...         n = n + 1
... 
>>> g = infinite_gen()
>>> g.next()
0
>>> g.next()
1
>>> g.next()
2
>>> g.next()
3
...

Benutzer-Avatar
Wernsee

Es gibt kein Java-Äquivalent.

Hier ist ein etwas erfundenes Beispiel:

#! /usr/bin/python
def  mygen(n):
    x = 0
    while x < n:
        x = x + 1
        if x % 3 == 0:
            yield x

for a in mygen(100):
    print a

Es gibt eine Schleife im Generator, die von 0 bis n läuft, und wenn die Schleifenvariable ein Vielfaches von 3 ist, ergibt sie die Variable.

Während jeder Iteration des for Schleife wird der Generator ausgeführt. Wenn es das erste Mal ist, dass der Generator ausgeführt wird, beginnt er am Anfang, andernfalls fährt er ab dem letzten Zeitpunkt fort, an dem er ausgegeben wurde.

  • Der letzte Absatz ist sehr wichtig: Der Zustand der Generatorfunktion wird jedes Mal ‘eingefroren’, wenn sie etw liefert, und bleibt beim nächsten Aufruf in genau demselben Zustand.

    – Johannes Charra

    18. November 2009 um 14:12 Uhr

  • Es gibt in Java kein syntaktisches Äquivalent zu einem “Generatorausdruck”, aber Generatoren – sobald Sie einen haben – sind im Wesentlichen nur ein Iterator (dieselben grundlegenden Eigenschaften wie ein Java-Iterator).

    – überdenken

    18. November 2009 um 14:21 Uhr

  • @overthink: Nun, Generatoren können andere Nebenwirkungen haben, die Java-Iteratoren nicht haben können. Wenn ich sagen sollte print "hello" nach dem x=x+1 In meinem Beispiel würde „Hallo“ 100 Mal ausgegeben, während der Körper der for-Schleife immer noch nur 33 Mal ausgeführt würde.

    – Wernsee

    18. November 2009 um 15:02 Uhr

  • @iWerner: Ziemlich sicher, dass der gleiche Effekt in Java erzielt werden könnte. Die Implementierung von next() im entsprechenden Java-Iterator müsste immer noch von 0 bis 99 suchen (unter Verwendung Ihres mygen(100)-Beispiels), sodass Sie System.out.println() jedes Mal verwenden könnten, wenn Sie wollten. Sie würden jedoch nur 33 Mal von next() zurückkehren. Was Java fehlt, ist die sehr handliche yield-Syntax, die wesentlich einfacher zu lesen (und zu schreiben) ist.

    – überdenken

    18. November 2009 um 15:54 Uhr

  • Ich habe es geliebt, diese eine Zeile def zu lesen und mich daran zu erinnern: Wenn es das erste Mal ist, dass der Generator ausgeführt wird, beginnt er am Anfang, andernfalls wird er von der vorherigen Zeit fortgesetzt, an der er ausgegeben wurde.

    – Iqra.

    9. Dezember 2019 um 6:03 Uhr

Benutzer-Avatar
Peter Hansen

Ich beschreibe Generatoren für diejenigen mit einem anständigen Hintergrund in Programmiersprachen und Computern gerne in Bezug auf Stack-Frames.

In vielen Sprachen gibt es einen Stapel, auf dem sich der aktuelle Stapel-“Rahmen” befindet. Der Stack-Rahmen enthält Speicherplatz, der für lokale Variablen der Funktion zugewiesen ist, einschließlich der an diese Funktion übergebenen Argumente.

Wenn Sie eine Funktion aufrufen, wird der aktuelle Ausführungspunkt (der “Programmzähler” oder ein Äquivalent) auf den Stack geschoben und ein neuer Stack-Frame erstellt. Die Ausführung geht dann zum Anfang der aufgerufenen Funktion über.

Bei regulären Funktionen gibt die Funktion irgendwann einen Wert zurück und der Stack wird “gepoppt”. Der Stapelrahmen der Funktion wird verworfen und die Ausführung an der vorherigen Position fortgesetzt.

Wenn eine Funktion ein Generator ist, kann sie einen Wert zurückgeben ohne der Stack-Frame wird mit der yield-Anweisung verworfen. Die Werte der lokalen Variablen und des Programmzählers innerhalb der Funktion bleiben erhalten. Dadurch kann der Generator zu einem späteren Zeitpunkt fortgesetzt werden, wobei die Ausführung von der yield-Anweisung fortgesetzt wird, und er kann mehr Code ausführen und einen anderen Wert zurückgeben.

Vor Python 2.5 war dies alles, was Generatoren taten. Python 2.5 hat die Möglichkeit hinzugefügt, Werte zurückzugeben in auch zum Generator. Dabei steht der übergebene Wert als Ausdruck zur Verfügung, der sich aus der yield-Anweisung ergibt, die vorübergehend die Kontrolle (und einen Wert) vom Generator zurückgegeben hat.

Der Hauptvorteil von Generatoren besteht darin, dass der “Zustand” der Funktion erhalten bleibt, im Gegensatz zu regulären Funktionen, bei denen Sie jedes Mal, wenn der Stapelrahmen verworfen wird, diesen gesamten “Zustand” verlieren. Ein sekundärer Vorteil besteht darin, dass ein Teil des Funktionsaufruf-Overheads (Erstellen und Löschen von Stack-Frames) vermieden wird, obwohl dies normalerweise ein kleiner Vorteil ist.

  • Der letzte Absatz ist sehr wichtig: Der Zustand der Generatorfunktion wird jedes Mal ‘eingefroren’, wenn sie etw liefert, und bleibt beim nächsten Aufruf in genau demselben Zustand.

    – Johannes Charra

    18. November 2009 um 14:12 Uhr

  • Es gibt in Java kein syntaktisches Äquivalent zu einem “Generatorausdruck”, aber Generatoren – sobald Sie einen haben – sind im Wesentlichen nur ein Iterator (dieselben grundlegenden Eigenschaften wie ein Java-Iterator).

    – überdenken

    18. November 2009 um 14:21 Uhr

  • @overthink: Nun, Generatoren können andere Nebenwirkungen haben, die Java-Iteratoren nicht haben können. Wenn ich sagen sollte print "hello" nach dem x=x+1 In meinem Beispiel würde „Hallo“ 100 Mal ausgegeben, während der Körper der for-Schleife immer noch nur 33 Mal ausgeführt würde.

    – Wernsee

    18. November 2009 um 15:02 Uhr

  • @iWerner: Ziemlich sicher, dass der gleiche Effekt in Java erzielt werden könnte. Die Implementierung von next() im entsprechenden Java-Iterator müsste immer noch von 0 bis 99 suchen (unter Verwendung Ihres mygen(100)-Beispiels), sodass Sie System.out.println() jedes Mal verwenden könnten, wenn Sie wollten. Sie würden jedoch nur 33 Mal von next() zurückkehren. Was Java fehlt, ist die sehr handliche yield-Syntax, die wesentlich einfacher zu lesen (und zu schreiben) ist.

    – überdenken

    18. November 2009 um 15:54 Uhr

  • Ich habe es geliebt, diese eine Zeile def zu lesen und mich daran zu erinnern: Wenn es das erste Mal ist, dass der Generator ausgeführt wird, beginnt er am Anfang, andernfalls wird er von der vorherigen Zeit fortgesetzt, an der er ausgegeben wurde.

    – Iqra.

    9. Dezember 2019 um 6:03 Uhr

Benutzer-Avatar
Peter Mortensen

Es hilft, eine klare Unterscheidung zwischen der Funktion foo und dem Generator foo(n) zu machen:

def foo(n):
    yield n
    yield n+1

foo ist eine Funktion. foo(6) ist ein Generatorobjekt.

Die typische Art, ein Generator-Objekt zu verwenden, ist in einer Schleife:

for n in foo(6):
    print(n)

Die Schleife wird gedruckt

# 6
# 7

Stellen Sie sich einen Generator als eine fortsetzbare Funktion vor.

yield benimmt sich wie return in dem Sinne, dass die gelieferten Werte vom Generator “zurückgegeben” werden. Anders als bei return wird jedoch beim nächsten Mal, wenn der Generator nach einem Wert gefragt wird, die Funktion des Generators, foo, dort fortgesetzt, wo sie aufgehört hat – nach der letzten yield-Anweisung – und läuft weiter, bis sie auf eine weitere yield-Anweisung trifft.

Hinter den Kulissen, wenn Sie anrufen bar=foo(6) Die Generator-Objektleiste ist für Sie definiert, um eine zu haben next Attribut.

Sie können es selbst aufrufen, um die von foo gelieferten Werte abzurufen:

next(bar)    # Works in Python 2.6 or Python 3.x
bar.next()   # Works in Python 2.5+, but is deprecated. Use next() if possible.

Wenn foo endet (und es keine ausgegebenen Werte mehr gibt), wird aufgerufen next(bar) wirft einen StopInteration-Fehler.

1054610cookie-checkGeneratoren in Python verstehen

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy