Generatorausdrücke vs. Listenverständnis

411

Wann sollten Sie Generatorausdrücke verwenden und wann sollten Sie Listenverständnisse in Python verwenden?

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

# List comprehension
[x*2 for x in range(256)]
Schreibgeschützt
quelle
27
könnte [exp for x in iter]nur Zucker sein list((exp for x in iter))? oder gibt es einen Ausführungsunterschied?
b0fh
1
Ich glaube, ich hatte eine relevante Frage. Können wir bei der Verwendung von Yield nur den Generatorausdruck einer Funktion verwenden oder müssen wir Yield für eine Funktion verwenden, um das Generatorobjekt zurückzugeben?
28
@ b0fh Sehr späte Antwort auf Ihren Kommentar: In Python2 gibt es einen winzigen Unterschied, die Schleifenvariable wird aus einem Listenverständnis austreten, während ein Generatorausdruck nicht leckt. Vergleichen Sie X = [x**2 for x in range(5)]; print xmit Y = list(y**2 for y in range(5)); print y, die zweite gibt einen Fehler. In Python3 ist ein Listenverständnis in der Tat der syntaktische Zucker für einen Generatorausdruck, der list()wie erwartet eingespeist wird, sodass die Schleifenvariable nicht mehr ausläuft .
Bas Swinckels
12
Ich würde vorschlagen, PEP 0289 zu lesen . Aufsummiert von „Das PEP einleitet Generator Ausdrücke als hohe Leistung, Speicher effizient Verallgemeinerung Listenkomprehensionen und Generatoren“ . Es enthält auch nützliche Beispiele für die Verwendung.
icc97
5
@ icc97 Ich bin auch acht Jahre zu spät zur Party und der PEP-Link war perfekt. Vielen Dank, dass Sie das leicht zu finden gemacht haben!
Eenblam

Antworten:

283

Johns Antwort ist gut (das Listenverständnis ist besser, wenn Sie etwas mehrmals durchlaufen möchten). Es ist jedoch auch erwähnenswert, dass Sie eine Liste verwenden sollten, wenn Sie eine der Listenmethoden verwenden möchten. Der folgende Code funktioniert beispielsweise nicht:

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 einmal iterieren. Wenn Sie die generierten Ergebnisse speichern und verwenden möchten, sind Sie mit einem Listenverständnis wahrscheinlich besser dran.

Da Leistung der häufigste Grund ist, eine über die andere zu wählen, ist mein Rat, sich keine Sorgen zu machen und nur eine auszuwählen. Wenn Sie feststellen, dass Ihr Programm zu langsam läuft, sollten Sie sich nur dann Gedanken über die Optimierung Ihres Codes machen.

Eli Courtwright
quelle
70
Manchmal muss man haben , um Generatoren verwenden - zum Beispiel, wenn Sie Koroutinen mit kooperativen Planungs mit Ertrag zu schreiben. Aber wenn Sie das tun, stellen Sie diese Frage wahrscheinlich nicht;)
kurzlebig
12
Ich weiß, dass dies alt ist, aber ich denke, es ist erwähnenswert, dass Generatoren (und alle iterierbaren) zu Listen mit der Erweiterung hinzugefügt werden können: a = [1, 2, 3] b = [4, 5, 6] a.extend(b)- a wird jetzt [1, 2, 3, 4, 5, 6] sein. (Können Sie Zeilenumbrüche in Kommentaren hinzufügen?)
jarvisteve
12
@jarvisteve Ihr Beispiel widerspricht den Worten, die Sie sagen. Hier gibt es auch einen schönen Punkt. Listen können mit Generatoren erweitert werden, aber es war sinnlos, daraus einen Generator zu machen. Generatoren können nicht mit Listen erweitert werden, und Generatoren sind nicht ganz iterabel. a = (x for x in range(0,10)), b = [1,2,3]zum Beispiel. a.extend(b)löst eine Ausnahme aus. b.extend(a)wird alles auswerten, in diesem Fall macht es keinen Sinn, es überhaupt zu einem Generator zu machen.
Slater Victoroff
4
@ SlaterTyranus Sie sind 100% korrekt, und ich habe Sie für die Genauigkeit bewertet. Dennoch denke ich, dass sein Kommentar eine nützliche Nichtantwort auf die Frage des OP ist, da er denjenigen helfen wird, die sich hier befinden, weil sie so etwas wie "Generator mit Listenverständnis kombinieren" in eine Suchmaschine eingegeben haben.
rbp
1
Würde der Grund für die Verwendung eines Generators zum einmaligen Durchlaufen nicht (z. B. meine Besorgnis über mangelnden Speicher überschreibt meine Sorge über das "Abrufen" von Werten nacheinander ) wahrscheinlich immer noch bei mehrmaliger Iteration zutreffen? Ich würde sagen, es könnte eine Liste nützlicher machen, aber ob das ausreicht, um die Bedenken hinsichtlich des Gedächtnisses aufzuwiegen, ist etwas anderes.
Rob Grant
181

Das Iterieren über den Generatorausdruck oder das Listenverständnis bewirkt dasselbe. Das Listenverständnis erstellt jedoch zuerst die gesamte Liste im Speicher, während der Generatorausdruck die Elemente im laufenden Betrieb erstellt, sodass Sie sie für sehr große (und auch unendliche!) Sequenzen verwenden können.

dF.
quelle
39
+1 für unendlich. Mit einer Liste können Sie das nicht tun, unabhängig davon, wie wenig Ihnen die Leistung am Herzen liegt.
Paul Draper
Können Sie mit der Verständnismethode unendliche Generatoren erstellen?
AnnanFay
5
@Annan Nur wenn Sie bereits Zugriff auf einen anderen unendlichen Generator haben. Zum Beispiel itertools.count(n)ist eine unendliche Folge von ganzen Zahlen, beginnend mit n, also (2 ** item for item in itertools.count(n))eine unendliche Folge der Potenzen des 2Beginns bei 2 ** n.
Kevin
2
Ein Generator löscht Elemente aus dem Speicher, nachdem sie wiederholt wurden. Wenn Sie also über große Datenmengen verfügen, möchten Sie diese beispielsweise nur anzeigen. Es ist kein Erinnerungsschwein. mit Generatoren werden Artikel 'nach Bedarf' verarbeitet. Wenn Sie an der Liste festhalten oder sie erneut durchlaufen möchten (speichern Sie also die Elemente), verwenden Sie das Listenverständnis.
j2emanue
102

Verwenden Sie Listenverständnisse, wenn das Ergebnis mehrmals wiederholt werden muss oder wenn Geschwindigkeit an erster Stelle steht. Verwenden Sie Generatorausdrücke, bei denen der Bereich groß oder unendlich ist.

Weitere Informationen finden Sie unter Generatorausdrücke und Listenverständnisse .

John Millikin
quelle
2
Dies wird wahrscheinlich ein wenig vom Thema abweichen, aber leider "nicht googlable" ... Was würde "vorrangig" in diesem Zusammenhang bedeuten? Ich bin kein englischer Muttersprachler ... :)
Guillermo Ares
6
@ GuillermoAres Dies ist das direkte Ergebnis von "googeln" für die Bedeutung von vorrangig: wichtiger als alles andere; höchste.
Sнаđошƒаӽ
1
listsSind sie also schneller als generatorAusdrücke? Beim Lesen der Antwort von dF stellte sich heraus, dass es umgekehrt war.
Hassan Baig
1
Es ist wahrscheinlich besser zu sagen, dass das Listenverständnis schneller ist, wenn der Bereich klein ist, aber mit zunehmender Skalierung wird es wertvoller, die Werte im laufenden Betrieb zu berechnen - gerade rechtzeitig für ihre Verwendung. Das macht ein Generatorausdruck.
Kyle
59

Der wichtige Punkt ist, dass das Listenverständnis eine neue Liste erstellt. Der Generator erstellt ein iterierbares Objekt, das das Quellmaterial im laufenden Betrieb "filtert", während Sie die Bits verbrauchen.

Stellen Sie sich vor, Sie haben eine 2-TB-Protokolldatei mit dem Namen "vastfile.txt" und möchten 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")]

Dadurch wird die gesamte Datei verschluckt, jede Zeile verarbeitet und die übereinstimmenden Zeilen in Ihrem Array gespeichert. Dieses Array kann daher bis zu 2 TB Inhalt enthalten. Das ist viel RAM und wahrscheinlich nicht praktisch für Ihre Zwecke.

Stattdessen können wir einen Generator verwenden, um einen "Filter" auf unseren Inhalt anzuwenden. Es werden tatsächlich keine Daten gelesen, bis wir beginnen, über das Ergebnis zu iterieren.

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

Es wurde noch nicht einmal eine einzige Zeile aus unserer Datei gelesen. Nehmen wir an, wir möchten unser Ergebnis noch weiter filtern:

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

Es wurde noch nichts gelesen, aber wir haben jetzt zwei Generatoren angegeben, die auf unsere Daten reagieren, wie wir es wünschen.

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 unsere forSchleife weiterhin zusätzliche Zeilen anfordert, long_entriesfordert der Generator Zeilen vom entry_linesGenerator an und gibt nur diejenigen zurück, deren Länge größer als 80 Zeichen ist. Der entry_linesGenerator fordert wiederum Zeilen (gefiltert wie angegeben) vom logfileIterator an, der wiederum die Datei liest.

Anstatt Daten in Form einer vollständig ausgefüllten Liste an Ihre Ausgabefunktion zu "pushen", geben Sie der Ausgabefunktion die Möglichkeit, Daten nur dann zu "ziehen", wenn sie benötigt werden. Dies ist in unserem Fall viel effizienter, aber nicht ganz so flexibel. Generatoren sind eine Möglichkeit, ein Durchgang; Die Daten aus der von uns gelesenen Protokolldatei werden sofort verworfen, sodass wir nicht zu einer vorherigen Zeile zurückkehren können. Auf der anderen Seite müssen wir uns keine Sorgen mehr machen, dass die Daten erhalten bleiben, wenn wir damit fertig sind.

tylerl
quelle
46

Der Vorteil eines Generatorausdrucks besteht darin, dass er weniger Speicher benötigt, da nicht die gesamte Liste auf einmal erstellt wird. Generatorausdrücke werden am besten verwendet, wenn die Liste ein Vermittler ist, z. B. das Summieren der Ergebnisse oder das Erstellen 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 besteht darin, dass die Liste nicht vollständig generiert wird und daher wenig Speicher verwendet wird (und auch schneller sein sollte).

Sie sollten jedoch Listenverständnisse verwenden, wenn das gewünschte Endprodukt eine Liste ist. Sie werden keine Speicher mit Generatorausdrücken speichern, da Sie die generierte Liste möchten. Sie haben auch den Vorteil, dass Sie alle Listenfunktionen wie sortiert oder umgekehrt verwenden können.

Zum Beispiel:

reversed( [x*2 for x in xrange(256)] )
Futter
quelle
9
In der Sprache gibt es einen Hinweis, dass Generatorausdrücke auf diese Weise verwendet werden sollen. Verliere die Klammern! sum(x*2 for x in xrange(256))
u0b34a0f6ae
8
sortedund reversedfunktionieren gut mit allen iterierbaren Generatorausdrücken.
März 75
1
Wenn Sie 2.7 und höher verwenden können, sieht dieses Beispiel für dict () als Diktatverständnis besser aus (das PEP dafür ist älter als die Generatorausdrücke PEP, aber die Landung dauerte länger)
Jürgen A. Erhard
14

Beachten Sie beim Erstellen eines Generators aus einem veränderlichen Objekt (wie einer Liste), dass der Generator zum Zeitpunkt der Verwendung des Generators und nicht zum Zeitpunkt der Erstellung des Generators nach dem Status der Liste bewertet wird:

>>> mylist = ["a", "b", "c"]
>>> gen = (elem + "1" for elem in mylist)
>>> mylist.clear()
>>> for x in gen: print (x)
# nothing

Wenn die Möglichkeit besteht, dass Ihre Liste geändert wird (oder ein veränderbares Objekt in dieser Liste), Sie jedoch den Status bei der Erstellung des Generators benötigen, müssen Sie stattdessen ein Listenverständnis verwenden.

Freaker
quelle
1
Und dies sollte die akzeptierte Antwort sein. Wenn Ihre Daten größer als der verfügbare Speicher sind, sollten Sie immer Generatoren verwenden, obwohl das Durchlaufen der Liste im Speicher möglicherweise schneller ist (Sie haben jedoch nicht genügend Speicher, um dies zu tun).
Marek Marczak
4

Manchmal kann man mit der Tee- Funktion von itertools davonkommen . Sie gibt mehrere Iteratoren für denselben Generator zurück, die unabhängig voneinander verwendet werden können.

Jacob Rigby
quelle
4

Ich verwende das Hadoop Mincemeat-Modul . Ich denke, dies ist ein großartiges Beispiel, um Folgendes zur Kenntnis zu nehmen:

import mincemeat

def mapfn(k,v):
    for w in v:
        yield 'sum',w
        #yield 'count',1


def reducefn(k,v): 
    r1=sum(v)
    r2=len(v)
    print r2
    m=r1/r2
    std=0
    for i in range(r2):
       std+=pow(abs(v[i]-m),2)  
    res=pow((std/r2),0.5)
    return r1,r2,res

Hier holt der Generator Zahlen aus einer Textdatei (bis zu 15 GB) und wendet einfache Berechnungen mit Hadoops Kartenreduzierung auf diese Zahlen an. Wenn ich nicht die Ertragsfunktion verwendet hätte, sondern ein Listenverständnis, hätte die Berechnung der Summen und des Durchschnitts viel länger gedauert (ganz zu schweigen von der Raumkomplexität).

Hadoop ist ein gutes Beispiel für die Nutzung aller Vorteile von Generatoren.

Murphy
quelle