2008-09-06 13 views
307

Wann sollten Sie Generator-Ausdrücke verwenden und wann sollten Sie Listen-Comprehensions in Python verwenden?Generator-Ausdrücke vs. Listen-Verständnis

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

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

könnte '[exp für x in iter]' sei einfach Zucker für 'list ((exp für x in iter))'? oder gibt es einen Ausführungsunterschied? – b0fh

+1

es denke, ich hatte eine relevante Frage, so dass bei der Verwendung von Ertrag können wir nur den Generator Ausdruck von einer Funktion verwenden, oder müssen wir Ausbeute für eine Funktion, um Generator-Objekt zurückgeben? –

+17

@ b0fh Sehr späte Antwort auf Ihren Kommentar: In Python2 gibt es einen winzigen Unterschied, die Schleifenvariable wird aus einem Listenverständnis herauslecken, während ein Generatorausdruck nicht leckt. Vergleiche 'X = [x ** 2 für x im Bereich (5)]; drucke x' mit 'Y = Liste (y ** 2 für y im Bereich (5)); print y', der zweite gibt einen Fehler. In Python3 ist ein List-Verständnis in der Tat der syntaktische Zucker für einen Generator-Ausdruck, der 'list()' wie erwartet zugeführt wird, so dass die Loop-Variable nicht mehr ausläuft (https://www.python.org/dev/). peps/pep-0289 # the-details). –

Antwort

219

Johns Antwort ist gut (diese Listenkomprehensionen sind besser, wenn Sie mehrmals über etwas iterieren wollen). 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 im Allgemeinen einen Generatorausdruck, wenn alles, was Sie tun, einmal wiederholt wird. Wenn Sie die generierten Ergebnisse speichern und verwenden möchten, sind Sie wahrscheinlich mit einem Listenverständnis besser dran.

Da Leistung der häufigste Grund ist, sich gegenseitig zu wählen, ist mein Rat, sich nicht darum zu kümmern und nur eines auszuwählen; Wenn Sie feststellen, dass Ihr Programm zu langsam läuft, dann sollten Sie nur zurückgehen und sich darum kümmern, Ihren Code zu optimieren.

+58

Manchmal * müssen * Sie Generatoren verwenden - zum Beispiel, wenn Sie Coroutinen mit kooperativer Terminplanung unter Verwendung von Yield schreiben. Aber wenn Sie das tun, stellen Sie wahrscheinlich diese Frage nicht;) – ephemient

+10

Ich weiß, dass das alt ist, aber ich denke, es ist erwähnenswert, dass Generatoren (und alle iterierbaren) zu Listen mit extend hinzugefügt werden können: 'a = [ 1, 2, 3] b = [4, 5, 6] a.extend (b) '- a wird nun [1, 2, 3, 4, 5, 6]. (Können Sie Zeilenumbrüche in Kommentaren hinzufügen ??) – jarvisteve

+11

@jarvisteve Ihr Beispiel täuscht über die Worte, die Sie sagen. Es gibt auch einen schönen Punkt hier. Listen können mit Generatoren erweitert werden, aber dann hat es keinen Sinn gemacht, sie zu einem Generator zu machen. Generatoren können nicht mit Listen erweitert werden, und Generatoren sind nicht ganz iterierbar. 'a = (x für x im Bereich (0,10)), b = [1,2,3]' zum Beispiel. 'a.extend (b)' löst eine Ausnahme aus. 'b.xtend (a)' wird alles von a auswerten, in welchem ​​Fall es keinen Sinn macht, es zu einem Generator zu machen. –

73

Verwenden Listenkomprehensionen, wenn das Ergebnis mehrmals iteriert werden muss, oder bei denen die Geschwindigkeit im Vordergrund steht. Verwenden Sie Generatorausdrücke, wenn der Bereich groß oder unendlich ist.

+0

Das wird wohl ein wenig off-topic sein, aber leider "un-googled" ... Was würde "paramount" in diesem Zusammenhang bedeuten? Ich bin kein englischer Muttersprachler ... :) –

+2

@GuillermoAres das ist das direkte Ergebnis von "googeln" für die Bedeutung von überragend: * wichtiger als alles andere; supreme. * –

+0

Also 'Listen' sind schneller als' generator' Ausdrücke? Nach dem Lesen der Antwort von dF kam es zu der Erkenntnis, dass es andersherum war. –

133

Iterating über die Generator Ausdruck oder die Liste Verständnis wird das gleiche tun. Allerdings wird die Liste Verständnis erstellen Sie die gesamte Liste im Speicher zuerst, während die Generator Ausdruck wird die Elemente im laufenden Betrieb, so dass Sie in der Lage, es für sehr große (und auch unendlich!) Sequenzen zu verwenden.

+21

+1 für unendlich. Sie können das nicht mit einer Liste machen, egal wie wenig Sie sich um die Leistung kümmern. –

+0

Können Sie unendliche Generatoren mit der Verständnismethode erstellen? – Annan

+4

@Annan Nur wenn Sie bereits Zugriff auf einen anderen unendlichen Generator haben. Zum Beispiel ist 'itertools.count (n)' eine unendliche Folge von ganzen Zahlen, beginnend mit n, so '(2 ** item für item in itertools.count (n)) 'wäre eine unendliche Folge von Potenzen von' 2 'beginnt bei' 2 ** n '. – Kevin

3

Manchmal kommt man mit der tee Funktion von itertools, es gibt mehrere Iteratoren für den gleichen Generator, die unabhängig voneinander verwendet werden können.

40

Der Vorteil eines Generatorausdrucks ist, dass er weniger Speicher benötigt, da er nicht die gesamte Liste auf einmal erstellt. 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 ist, dass die Liste nicht vollständig erzeugt wird, und somit wenig Speicher verwendet wird (und sollte auch schneller sein)

sollten Sie allerdings den Einsatz Listen Sie auf, wenn das gewünschte Endprodukt eine Liste ist. Sie werden kein Memeory mit Generatorausdrücken speichern, da Sie die generierte Liste haben wollen. Sie haben auch den Vorteil, dass Sie beliebige Listenfunktionen wie sortiert oder umgekehrt verwenden können.

Zum Beispiel:

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

Es gibt einen Hinweis in der Sprache, dass Generatorausdrücke auf diese Weise verwendet werden sollen. Verliere die Klammern! 'sum (x * 2 für x in xrange (256))' – u0b34a0f6ae

+3

Das sollte "dict (((k, some_func (k)) für k in some_list_of_keys) sein." Natürlich ist die 2.7+ dict/set Verständnis Syntax gerade süßer –

+6

'sortierte' und' umgekehrte' funktionieren gut auf jedem iterierbaren, Generator Ausdrücke enthalten – marr75

43

Der wichtige Punkt ist, dass die Liste Verständnis eine neue Liste erstellt. Der Generator erzeugt ein iterierbares Objekt, das das Quellmaterial während des Verbrauchs der Bits "filtert".

Stellen Sie sich vor, Sie haben eine 2TB-Protokolldatei namens "rianglefile.txt", und Sie möchten den Inhalt und die Länge für alle Zeilen, die mit dem Wort "ENTRY" beginnen.

Also versuchen Sie durch das Schreiben einer Liste Verständnis anfangen:

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

Diese schlürft die gesamte Datei auf, verarbeitet jede Zeile und speichert die passenden Zeilen in der Matrix. Dieses Array könnte daher bis zu 2 TB Inhalt enthalten. Das ist viel RAM und wahrscheinlich nicht praktisch für Ihre Zwecke.

Stattdessen können wir stattdessen einen Generator verwenden, um einen "Filter" auf unseren Inhalt anzuwenden. Es werden keine Daten gelesen, bis wir mit dem Iterieren des Ergebnisses beginnen.

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

Noch nicht einmal eine Zeile wurde aus unserer Datei gelesen. In der Tat, sagen wir unser Ergebnis weiter filtern möchten:

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

Immer noch nichts gelesen wurde, aber wir haben jetzt zwei Generatoren angegeben, die auf unsere Daten handeln, wie wir wollen.

Lets unsere gefilterten Linien auf eine andere Datei schreiben:

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

Jetzt wir die Eingabedatei lesen. Da unsere for Schleife weiterhin zusätzliche Leitungen anfordert, fordert der long_entries Generator Leitungen vom Generator entry_lines an und gibt nur diejenigen zurück, deren Länge mehr als 80 Zeichen beträgt. Und wiederum fordert der Generator entry_lines Zeilen an (gefiltert wie angezeigt) von dem logfile Iterator, der wiederum 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 "abzuholen", wenn sie benötigt wird. Dies ist in unserem Fall viel effizienter, aber nicht ganz so flexibel. Generatoren sind ein Weg, ein Durchgang; Die Daten aus der Protokolldatei, die wir gelesen haben, werden sofort verworfen, sodass wir nicht zu einer vorherigen Zeile zurückkehren können. Auf der anderen Seite müssen wir uns nicht darum kümmern, Daten zu speichern, sobald wir damit fertig sind.

4

Ich benutze die Hadoop Mincemeat module. Ich denke, dass dies ein gutes Beispiel ist eine 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 wird der Generator bekommt Zahlen aus einer Textdatei (so groß wie 15 GB) und wendet einfache mathematische auf diesen Zahlen mit Hadoop des Karten reduzieren. Wenn ich nicht die Yield-Funktion, sondern ein Listenverständnis verwendet hätte, hätte es viel länger gedauert, die Summen und den Durchschnitt zu berechnen (ganz zu schweigen von der Komplexität des Platzes).

Hadoop ist ein großartiges Beispiel für die Nutzung aller Vorteile von Generatoren.

9

Wenn ein Generator von einem veränderlichen Objekt zu schaffen (wie eine Liste) beachten Sie, dass der Generator auf dem Stand der Liste zum Zeitpunkt zum Zeitpunkt des Generator zu verwenden, nicht von der Schaffung des Generators erhält ausgewertet:

Wenn es eine Chance gibt, dass Ihre Liste geändert wird (oder ein veränderbares Objekt in dieser Liste), aber Sie den Status bei der Erstellung des Generators benötigen, müssen Sie stattdessen ein Listenverständnis verwenden.

Verwandte Themen