Frage

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

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

# List comprehension
[x*2 for x in range(256)]
War es hilfreich?

Lösung

Johns Antwort ist gut (das Listenverständnis ist besser, wenn Sie etwas mehrmals durchlaufen möchten).Beachten Sie jedoch auch, 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

Grundsätzlich sollten Sie einen Generatorausdruck verwenden, wenn Sie nur einmal iterieren.Wenn Sie die generierten Ergebnisse speichern und verwenden möchten, ist ein Listenverständnis wahrscheinlich besser geeignet.

Da die Leistung der häufigste Grund ist, sich für eines der anderen zu entscheiden, rate ich Ihnen, sich darüber keine Sorgen zu machen und sich einfach für eines zu entscheiden.Wenn Sie feststellen, dass Ihr Programm zu langsam läuft, sollten Sie nur dann zurückgehen und sich Gedanken über die Optimierung Ihres Codes machen.

Andere Tipps

Iterieren über die Generatorausdruck oder der Listenverständnis werde das Gleiche tun.Allerdings ist die Listenverständnis erstellt zunächst die gesamte Liste im Speicher, während die Generatorausdruck erstellt die Elemente im Handumdrehen, sodass Sie sie für sehr große (und auch unendliche!) Sequenzen verwenden können.

Verwenden Sie Listenverständnisse, wenn das Ergebnis mehrmals iteriert werden muss oder wenn Geschwindigkeit von größter Bedeutung ist.Verwenden Sie Generatorausdrücke, wenn der Bereich groß oder unendlich ist.

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 große Protokolldatei mit dem Namen „hugefile.txt“ und möchten den Inhalt und die Länge aller Zeilen, die mit dem Wort „ENTRY“ beginnen.

Versuchen Sie also zunächst, eine Liste zum Verstä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 geschlürft, jede Zeile verarbeitet und die passenden Zeilen in Ihrem Array gespeichert.Dieses Array könnte daher bis zu 2 TB Inhalt enthalten.Das ist viel 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 tatsächlich 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 nicht einmal eine einzige Zeile aus unserer Datei gelesen.Angenommen, wir möchten unser Ergebnis sogar 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 Wir lesen die Eingabedatei.Als unsere for Schleife fordert weiterhin zusätzliche Zeilen an, die long_entries Generator fordert Leitungen vom entry_lines Generator, der nur diejenigen zurückgibt, deren Länge mehr als 80 Zeichen beträgt.Und im Gegenzug die entry_lines Der Generator fordert Zeilen (wie angegeben gefiltert) vom an logfile Iterator, der wiederum die Datei liest.

Anstatt also Daten in Form einer vollständig ausgefüllten Liste an Ihre Ausgabefunktion zu „schieben“, geben Sie der Ausgabefunktion die Möglichkeit, Daten nur dann zu „abrufen“, wenn sie benötigt werden.Das ist in unserem Fall deutlich effizienter, aber nicht ganz so flexibel.Generatoren funktionieren in einer Richtung und in einem Durchgang.Die von uns gelesenen Daten aus der Protokolldatei werden sofort verworfen, sodass wir nicht zu einer vorherigen Zeile zurückkehren können.Andererseits müssen wir uns keine Sorgen mehr darüber machen, dass die Daten gespeichert bleiben, wenn wir damit fertig sind.

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. zum Summieren der Ergebnisse oder zum 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 somit wenig Speicher verbraucht wird (und auch schneller sein sollte)

Sie sollten jedoch Listenverständnisse verwenden, wenn das gewünschte Endprodukt eine Liste ist.Sie werden mit Generatorausdrücken keinen Speicher speichern, da Sie die generierte Liste benötigen.Sie profitieren außerdem davon, dass Sie alle Listenfunktionen wie „sortiert“ oder „umgekehrt“ verwenden können.

Zum Beispiel:

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

Beachten Sie beim Erstellen eines Generators aus einem veränderlichen Objekt (z. B. einer Liste), dass der Generator zum Zeitpunkt der Verwendung des Generators anhand des Status der Liste bewertet wird, nicht zum Zeitpunkt der Erstellung des Generators:

>>> 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änderliches Objekt in dieser Liste ist), Sie aber den Status bei der Erstellung des Generators benötigen, müssen Sie stattdessen ein Listenverständnis verwenden.

Ich verwende das Hadoop Mincemeat-Modul.Ich denke, das ist ein tolles Beispiel, das man sich merken sollte:

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 ruft der Generator Zahlen aus einer Textdatei (bis zu 15 GB) ab und wendet mithilfe von Hadoops Map-Reduce einfache Mathematik auf diese Zahlen an.Wenn ich nicht die Yield-Funktion, sondern ein Listenverständnis verwendet hätte, hätte die Berechnung der Summen und des Durchschnitts viel länger gedauert (ganz zu schweigen von der Platzkomplexität).

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

Manchmal kommt man damit durch T-Stück Funktion von itertools, gibt es mehrere Iteratoren für denselben Generator zurück, die unabhängig voneinander verwendet werden können.

Wie wäre es, wenn Sie [(exp for x in iter)] verwenden, um beides zu nutzen?Leistung durch Generatorverständnis sowie Listenmethoden

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top