528 Stimmen

Generatorausdrücke vs. Listenauffassungen

Wann sollte man in Python Generatorausdrücke und wann Listenauflösungen verwenden?

# Generator expression
(x*2 for x in range(256))

# List comprehension
[x*2 for x in range(256)]

358voto

Eli Courtwright Punkte 174547

Johns Antwort gut ist (dass Listenauffassungen besser sind, wenn man etwas mehrfach wiederholen will). Es ist jedoch auch erwähnenswert, dass Sie eine Liste verwenden sollten, wenn Sie eine der Listenmethoden verwenden möchten. Der folgende Code wird zum Beispiel nicht funktionieren:

def gen():
    return (something for something in get_some_stuff())

print gen()[:2]     # generators don't support indexing or slicing
print [5,6] + gen() # generators can't be added to lists

Verwenden Sie grundsätzlich einen Generatorausdruck, wenn Sie nur eine Iteration durchführen wollen. Wenn Sie die generierten Ergebnisse speichern und verwenden möchten, sind Sie mit einer Listenverarbeitung wahrscheinlich besser dran.

Da die Leistung der häufigste Grund ist, sich für die eine oder die andere Variante zu entscheiden, rate ich Ihnen, sich keine Gedanken darüber zu machen und einfach eine Variante zu wählen.

249voto

dF. Punkte 70587

Iteration über die Generatorausdruck oder die Listenverstehen wird das Gleiche tun. Allerdings ist die Listenverstehen wird zuerst die gesamte Liste im Speicher erstellt, während die Generatorausdruck erstellt die Elemente im laufenden Betrieb, so dass Sie es für sehr große (und auch unendliche!) Sequenzen verwenden können.

140voto

John Millikin Punkte 190278

Verwenden Sie Listenauffassungen, wenn das Ergebnis mehrfach iteriert werden muss oder wenn es auf Geschwindigkeit ankommt. Verwenden Sie Generatorausdrücke, wenn der Bereich groß oder unendlich ist.

Siehe Generatorausdrücke und Listenauffassungen für weitere Informationen.

72voto

tylerl Punkte 29162

Wichtig ist, dass das Listenverständnis eine neue Liste erstellt. Der Generator erstellt ein iterierbares Objekt, das das Quellmaterial "on-the-fly" filtert, während Sie die Bits konsumieren.

Stellen Sie sich vor, Sie haben eine 2 TB große Protokolldatei mit dem Namen "hugefile.txt", und Sie wollen den Inhalt und die Länge aller Zeilen, die mit dem Wort "ENTRY" beginnen.

Versuchen Sie also zunächst, ein Listenverständnis zu schreiben:

logfile = open("hugefile.txt","r")
entry_lines = [(line,len(line)) for line in logfile if line.startswith("ENTRY")]

Dies schlürft die gesamte Datei, verarbeitet jede Zeile und speichert die passenden Zeilen in Ihrem Array. Dieses Array kann also bis zu 2 TB an Inhalten enthalten. Das ist eine Menge RAM und für Ihre Zwecke wahrscheinlich nicht praktikabel.

Stattdessen können wir einen Generator verwenden, um einen "Filter" auf unsere Inhalte anzuwenden. Es werden keine Daten gelesen, bis wir mit der Iteration über das Ergebnis beginnen.

logfile = open("hugefile.txt","r")
entry_lines = ((line,len(line)) for line in logfile if line.startswith("ENTRY"))

Es wurde noch keine einzige Zeile aus unserer Datei gelesen. Sagen wir, wir wollen unser Ergebnis noch weiter filtern:

long_entries = ((line,length) for (line,length) in entry_lines if length > 80)

Noch immer wurde nichts ausgelesen, aber wir haben jetzt zwei Generatoren angegeben, die unsere Daten nach unseren Wünschen verarbeiten werden.

Schreiben wir unsere gefilterten Zeilen in eine andere Datei:

outfile = open("filtered.txt","a")
for entry,length in long_entries:
    outfile.write(entry)

Jetzt lesen wir die Eingabedatei. Da unser for Schleife weiterhin zusätzliche Zeilen anfordert, wird die long_entries Generator verlangt Leitungen vom entry_lines Generator, der nur diejenigen zurückgibt, deren Länge mehr als 80 Zeichen beträgt. Und im Gegenzug wird der entry_lines Generator fordert Zeilen (wie angegeben gefiltert) von der logfile Iterator, der seinerseits die Datei liest.

Anstatt also Daten in Form einer vollständig ausgefüllten Liste an Ihre Ausgabefunktion zu "pushen", geben Sie der Ausgabefunktion eine Möglichkeit, Daten nur dann zu "ziehen", wenn sie benötigt werden. Das ist in unserem Fall viel effizienter, aber nicht ganz so flexibel. Generatoren sind ein Weg, ein Durchgang; die Daten aus der gelesenen Protokolldatei werden sofort verworfen, so dass wir nicht zu einer früheren Zeile zurückkehren können. Andererseits müssen wir uns keine Gedanken darüber machen, wie wir die Daten aufbewahren, wenn wir mit ihnen fertig sind.

55voto

Chuck Punkte 816

Der Vorteil eines Generatorausdrucks ist, dass er weniger Speicherplatz benötigt, da er nicht die gesamte Liste auf einmal aufbaut. Generatorausdrücke werden am besten verwendet, wenn die Liste ein Zwischenprodukt ist, z. B. bei der Summierung der Ergebnisse oder bei der Erstellung eines Diktats aus den Ergebnissen.

Zum Beispiel:

sum(x*2 for x in xrange(256))

dict( (k, some_func(k)) for k in some_list_of_keys )

Der Vorteil dabei ist, dass die Liste nicht vollständig generiert wird und somit wenig Speicherplatz benötigt wird (und auch schneller sein sollte)

Sie sollten jedoch Listenauffassungen verwenden, wenn das gewünschte Endprodukt eine Liste ist. Mit Generatorausdrücken werden Sie keinen Speicherplatz sparen, da Sie die erzeugte Liste benötigen. Außerdem haben Sie den Vorteil, dass Sie eine der Listenfunktionen wie sorted oder reversed verwenden können.

Zum Beispiel:

reversed( [x*2 for x in xrange(256)] )

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X