Gibt es einen Grund, lieber das map()
Listenverständnis zu verwenden oder umgekehrt? Ist einer von ihnen im Allgemeinen effizienter oder wird er im Allgemeinen als pythonischer angesehen als der andere?
python
list-comprehension
map-function
TimothyAWiseman
quelle
quelle
Antworten:
map
kann in einigen Fällen mikroskopisch schneller sein (wenn Sie KEIN Lambda für diesen Zweck herstellen, aber dieselbe Funktion in Map und Listcomp verwenden). In anderen Fällen kann das Listenverständnis schneller sein, und die meisten (nicht alle) Pythonisten betrachten sie als direkter und klarer.Ein Beispiel für den winzigen Geschwindigkeitsvorteil der Karte bei genau derselben Funktion:
Ein Beispiel dafür, wie der Leistungsvergleich vollständig umgekehrt wird, wenn die Karte ein Lambda benötigt:
quelle
map(operator.attrgetter('foo'), objs)
leichter zu lesen als[o.foo for o in objs]
?!o
hier einzuführen , und Ihre Beispiele zeigen, warum.str()
Beispiel einen Punkt hat .Fälle
map
, obwohl sie als "unpythonisch" angesehen wird. Zum Beispielmap(sum, myLists)
ist eleganter / knapper als[sum(x) for x in myLists]
. Sie erhalten die Eleganz, keine Dummy-Variable (z. B.sum(x) for x...
odersum(_) for _...
odersum(readableName) for readableName...
) erstellen zu müssen, die Sie zweimal eingeben müssen, nur um zu iterieren. Das gleiche Argument gilt fürfilter
undreduce
und alles aus demitertools
Modul: Wenn Sie bereits eine Funktion zur Hand haben, können Sie eine funktionale Programmierung durchführen. Dies verbessert die Lesbarkeit in einigen Situationen und verliert sie in anderen (z. B. unerfahrene Programmierer, mehrere Argumente) ... aber die Lesbarkeit Ihres Codes hängt in hohem Maße von Ihren Kommentaren ab.map
Funktion als reine abstrakte Funktion verwenden, während Sie eine funktionale Programmierung durchführen, bei der Sie Mappingmap
oder Currying durchführenmap
oder auf andere Weise davon profitieren, übermap
eine Funktion zu sprechen . In Haskell beispielsweisefmap
verallgemeinert eine Funktorschnittstelle mit dem Namen Generalizing die Zuordnung über eine beliebige Datenstruktur. Dies ist in Python sehr ungewöhnlich, da die Python-Grammatik Sie dazu zwingt, im Generatorstil über Iteration zu sprechen. Sie können es nicht einfach verallgemeinern. (Dies ist manchmal gut und manchmal schlecht.) Sie können wahrscheinlich seltene Python-Beispiele finden, bei denenmap(f, *lists)
es sinnvoll ist, dies zu tun. Das nächste Beispiel, das ich finden kannsumEach = partial(map,sum)
, ist ein Einzeiler, der in etwa gleichwertig ist mit:for
Schleife verwenden : Sie können natürlich auch nur eine for-Schleife verwenden. Obwohl sie aus Sicht der funktionalen Programmierung nicht so elegant sind, machen nicht-lokale Variablen den Code in wichtigen Programmiersprachen wie Python manchmal klarer, da die Leute es sehr gewohnt sind, Code auf diese Weise zu lesen. For-Schleifen sind im Allgemeinen auch am effizientesten, wenn Sie lediglich eine komplexe Operation ausführen, bei der keine Liste erstellt wird, für die Listenverständnisse und Karten optimiert sind (z. B. Summieren oder Erstellen eines Baums usw.) - zumindest effizient in Bezug auf das Gedächtnis (nicht unbedingt in Bezug auf die Zeit, wo ich im schlimmsten Fall einen konstanten Faktor erwarten würde, abgesehen von einigen seltenen pathologischen Schluckauf bei der Müllabfuhr)."Pythonismus"
Ich mag das Wort "pythonic" nicht, weil ich nicht finde, dass pythonic in meinen Augen immer elegant ist. Dennoch
map
undfilter
und ähnliche Funktionen (wie das sehr nützlichitertools
Modul) sind wahrscheinlich unpythonic in Bezug auf Stil betrachtet.Faulheit
In Bezug auf die Effizienz kann MAP , wie die meisten funktionalen Programmierkonstrukte, faul sein und ist in Python tatsächlich faul. Das bedeutet, dass Sie dies tun können (in Python3 ) und Ihr Computer nicht über genügend Speicher verfügt und alle nicht gespeicherten Daten verliert:
Versuchen Sie dies mit einem Listenverständnis:
Beachten Sie, dass Listenverständnisse ebenfalls von Natur aus faul sind, Python sie jedoch als nicht faul implementiert hat . Trotzdem unterstützt Python das Verständnis fauler Listen in Form von Generatorausdrücken wie folgt:
Sie können sich die
[...]
Syntax grundsätzlich als Übergabe eines Generatorausdrucks an den Listenkonstruktor vorstellenlist(x for x in range(5))
.Kurzes erfundenes Beispiel
Listenverständnisse sind nicht faul und erfordern daher möglicherweise mehr Speicher (es sei denn, Sie verwenden Generatorverständnisse). Die eckigen Klammern
[...]
machen die Dinge oft deutlich, besonders wenn sie in Klammern stehen. Auf der anderen Seite ist man manchmal wortreich wie beim Tippen[x for x in...
. Solange Sie Ihre Iteratorvariablen kurz halten, ist das Listenverständnis normalerweise klarer, wenn Sie Ihren Code nicht einrücken. Sie können Ihren Code jedoch jederzeit einrücken.oder aufbrechen:
Effizienzvergleich für Python3
map
ist jetzt faul:Wenn Sie daher nicht alle Ihre Daten verwenden oder nicht
map
im Voraus wissen, wie viele Daten Sie in Python3 (und Generatorausdrücken in Python2 oder Python3) benötigen, wird die Berechnung ihrer Werte bis zum letzten erforderlichen Moment vermieden. Normalerweise überwiegt der Overhead bei der Verwendungmap
. Der Nachteil ist, dass dies in Python im Gegensatz zu den meisten funktionalen Sprachen sehr begrenzt ist: Sie erhalten diesen Vorteil nur, wenn Sie von links nach rechts "in der richtigen Reihenfolge" auf Ihre Daten zugreifen, da Python-Generator-Ausdrücke nur in der Reihenfolge ausgewertet werden könnenx[0], x[1], x[2], ...
.Nehmen wir jedoch an, wir haben eine vorgefertigte Funktion, die
f
wir möchtenmap
, und wir ignorieren die Faulheit,map
indem wir sofort die Bewertung mit erzwingenlist(...)
. Wir erhalten einige sehr interessante Ergebnisse:Die Ergebnisse liegen in der Form AAA / BBB / CCC vor, bei der A mit einer Intel-Workstation von ca. 2010 mit Python 3 ausgeführt wurde.?.? Und B und C mit einer AMD-Workstation von ca. 2013 mit Python 3.2.1 durchgeführt wurden. mit extrem unterschiedlicher Hardware. Das Ergebnis scheint zu sein, dass das Karten- und Listenverständnis in der Leistung vergleichbar ist, was am stärksten von anderen Zufallsfaktoren beeinflusst wird. Das einzige, was wir sagen können, scheint zu sein, dass seltsamerweise, obwohl wir erwarten, dass Listenverständnisse
[...]
besser funktionieren als Generatorausdrücke(...)
,map
AUCH effizienter als Generatorausdrücke sind (wiederum unter der Annahme, dass alle Werte ausgewertet / verwendet werden).Es ist wichtig zu wissen, dass diese Tests eine sehr einfache Funktion (die Identitätsfunktion) annehmen. Dies ist jedoch in Ordnung, da bei einer komplizierten Funktion der Leistungsaufwand im Vergleich zu anderen Faktoren im Programm vernachlässigbar wäre. (Es kann immer noch interessant sein, mit anderen einfachen Dingen wie zu testen
f=lambda x:x+x
)Wenn Sie mit dem Lesen von Python-Assemblys vertraut sind, können Sie mithilfe des
dis
Moduls feststellen , ob dies tatsächlich hinter den Kulissen geschieht:Es scheint besser zu sein,
[...]
Syntax zu verwenden alslist(...)
. Leider ist diemap
Klasse für die Demontage etwas undurchsichtig, aber wir können mit unserem Geschwindigkeitstest dafür sorgen.quelle
map
und zu sagenfilter
zusammen mit Standardbibliothekitertools
sind von Natur aus schlechter Stil. Sofern GvR nicht tatsächlich sagt, dass sie entweder ein schrecklicher Fehler oder nur für die Leistung waren, ist die einzige natürliche Schlussfolgerung, wenn "Pythonicness" dies sagt, es als dumm zu vergessen ;-)map
/ wärefilter
eine großartige Idee für Python 3 , und nur eine Rebellion anderer Pythonisten hielt sie im eingebauten Namespace (während siereduce
verschoben wurdenfunctools
). Ich persönlich bin anderer Meinung (map
und binfilter
mit vordefinierten, insbesondere eingebauten Funktionen einverstanden , benutze sie nur nie, wenn einelambda
benötigt wird), aber GvR nennt sie seit Jahren grundsätzlich nicht Pythonic.itertools
? Der Teil, den ich aus dieser Antwort zitiere, ist die Hauptbehauptung, die mich verwirrt. Ich weiß nicht, ob in seiner idealen Welt,map
undfilter
würde zuitertools
(oderfunctools
) oder ganz gehen, aber was auch immer der Fall ist, wenn man sagt, dassitertools
das in seiner Gesamtheit unpythonisch ist, dann weiß ich nicht wirklich, was "Pythonisch" ist soll bedeuten, aber ich glaube nicht, dass es etwas ähnliches sein kann wie "was GvR den Leuten empfiehlt".map
/ angesprochenfilter
, nichtitertools
. Funktionale Programmierung ist perfekt Pythonic (itertools
,functools
undoperator
wurden alle speziell mit der funktionalen Programmierung konzipiert, und ich verwende funktionelle Idiome in Python die ganze Zeit), unditertools
bietet Funktionen , die ein Schmerz selbst zu implementieren sein würde, es ist speziellmap
undfilter
redundant mit Generator Ausdrücke das ließ Guido sie hassen.itertools
war schon immer in Ordnung.Python 2: Sie sollten
map
undfilter
anstelle von Listenverständnissen verwenden.Ein objektiver Grund, warum Sie sie bevorzugen sollten, obwohl sie nicht "Pythonic" sind, ist folgender:
Sie benötigen Funktionen / Lambdas als Argumente, die einen neuen Bereich einführen .
Ich bin mehr als einmal davon gebissen worden:
aber wenn ich stattdessen gesagt hätte:
dann wäre alles in Ordnung gewesen.
Man könnte sagen, ich war dumm, denselben Variablennamen im selben Bereich zu verwenden.
Ich war nicht. Der Code war ursprünglich in Ordnung - die beiden
x
s waren nicht im gleichen Bereich.Erst nachdem ich den inneren Block in einen anderen Abschnitt des Codes verschoben hatte , trat das Problem auf (sprich: Problem während der Wartung, nicht Entwicklung), und ich habe es nicht erwartet.
Ja, wenn Sie diesen Fehler nie machen, ist das Listenverständnis eleganter.
Aber aus persönlicher Erfahrung (und weil ich gesehen habe, wie andere den gleichen Fehler gemacht haben) habe ich es so oft gesehen, dass ich denke, dass es den Schmerz nicht wert ist, den Sie durchmachen müssen, wenn sich diese Fehler in Ihren Code einschleichen.
Fazit:
Verwenden Sie
map
undfilter
. Sie verhindern subtile, schwer zu diagnostizierende Fehler im Zusammenhang mit dem Bereich.Randnotiz:
Vergessen Sie nicht,
imap
undifilter
(initertools
) in Betracht zu ziehen, ob sie für Ihre Situation geeignet sind!quelle
map
und / oderfilter
. Wenn überhaupt, ist die direkteste und logischste Übersetzung, um Ihr Problem zu vermeiden, nichtmap(lambda x: x ** 2, numbers)
ein Generatorausdruck,list(x ** 2 for x in numbers)
der nicht leckt, wie JeromeJ bereits betont hat. Schauen Sie Mehrdad, nehmen Sie nicht so persönlich eine Ablehnung, ich bin nur stark anderer Meinung als Ihre Argumentation hier.Tatsächlich
map
verhalten sich Listenverständnisse in der Python 3-Sprache ganz anders. Schauen Sie sich das folgende Python 3-Programm an:Sie können erwarten, dass die Zeile "[1, 4, 9]" zweimal gedruckt wird, aber stattdessen wird "[1, 4, 9]" gefolgt von "[]" gedruckt. Das erste Mal, wenn Sie es betrachten
squares
, scheint es sich als eine Folge von drei Elementen zu verhalten, das zweite Mal als eine leere.In der Python 2-Sprache wird
map
eine einfache alte Liste zurückgegeben, genau wie es das Listenverständnis in beiden Sprachen tut. Der springende Punkt ist, dass der Rückgabewert vonmap
in Python 3 (undimap
in Python 2) keine Liste ist - es ist ein Iterator!Die Elemente werden verbraucht, wenn Sie über einen Iterator iterieren, anders als wenn Sie über eine Liste iterieren. Deshalb
squares
sieht es in der letztenprint(list(squares))
Zeile leer aus .Zusammenfassen:
quelle
map
eine Datenstruktur erzeugen, keinen Iterator. Aber vielleicht sind faule Iteratoren einfacher als faule Datenstrukturen. Denkanstöße. Danke @MnZrKIch finde, Listenverständnisse sind im Allgemeinen ausdrucksvoller für das, was ich versuche, als
map
- beide schaffen es, aber das erstere erspart die mentale Belastung, zu versuchen, zu verstehen, was ein komplexerlambda
Ausdruck sein könnte.Es gibt auch irgendwo ein Interview (ich kann es nicht ohne weiteres finden), in dem Guido
lambda
s und die funktionalen Funktionen als das auflistet, was er am meisten bedauert, wenn er in Python aufgenommen wird, sodass Sie das Argument vorbringen können, dass sie aufgrund ihrer Tugend nicht pythonisch sind davon.quelle
const
Schlüsselwort in C ++ ein großer Triumph in dieser Richtung.lambda
wurden sie so lahm gemacht (keine Aussagen ..), dass sie schwierig zu bedienen und sowieso begrenzt sind.Hier ist ein möglicher Fall:
gegen:
Ich vermute, dass zip () ein unglücklicher und unnötiger Aufwand ist, dem Sie sich hingeben müssen, wenn Sie darauf bestehen, Listenverständnisse anstelle der Karte zu verwenden. Wäre toll, wenn jemand dies positiv oder negativ klarstellt.
quelle
zip
faul machen, indem Sieitertools.izip
map(operator.mul, list1, list2)
. Auf diesen sehr einfachen Ausdrücken auf der linken Seite wird das Verständnis ungeschickt.Wenn Sie asynchronen, parallelen oder verteilten Code schreiben möchten, ziehen Sie wahrscheinlich
map
ein Listenverständnis vor - da die meisten asynchronen, parallelen oder verteilten Pakete einemap
Funktion zum Überladen von Pythons bietenmap
. Wenn Sie dann die entsprechendemap
Funktion an den Rest Ihres Codes übergeben, müssen Sie möglicherweise nicht Ihren ursprünglichen Seriencode ändern, damit er parallel ausgeführt wird (usw.).quelle
Da Python 3
map()
ein Iterator ist, müssen Sie bedenken, was Sie benötigen: einen Iterator oder einlist
Objekt.Wie bereits erwähnt ,
map()
ist @AlexMartelli nur dann schneller als das Listenverständnis, wenn Sie keinelambda
Funktion verwenden.Ich werde Ihnen einige Zeitvergleiche vorstellen.
Python 3.5.2 und CPython
Ich habe Jupiter-Notebook und insbesondere den
%timeit
eingebauten magischen Befehl verwendet.Messungen : s == 1000 ms == 1000 * 1000 µs = 1000 * 1000 * 1000 ns
Installieren:
Eingebaute Funktion:
lambda
Funktion:Es gibt auch einen Generatorausdruck, siehe PEP-0289 . Also dachte ich, es wäre nützlich, es zum Vergleich hinzuzufügen
Sie benötigen
list
Objekt:Verwenden Sie das Listenverständnis, wenn es sich um eine benutzerdefinierte Funktion handelt, und verwenden Sie es,
list(map())
wenn eine integrierte Funktion vorhanden istSie brauchen kein
list
Objekt, Sie brauchen nur ein iterierbares:Immer benutzen
map()
!quelle
Ich habe einen Schnelltest durchgeführt, bei dem drei Methoden zum Aufrufen der Methode eines Objekts verglichen wurden. Der Zeitunterschied ist in diesem Fall vernachlässigbar und hängt von der jeweiligen Funktion ab (siehe Antwort von @Alex Martelli ). Hier habe ich mir folgende Methoden angesehen:
Ich habe mir Listen (in der Variablen gespeichert
vals
) von Ganzzahlen (Pythonint
) und Gleitkommazahlen (Pythonfloat
) angesehen, um die Listengröße zu erhöhen. Die folgende Dummy-KlasseDummyNum
wird berücksichtigt:Insbesondere die
add
Methode. Das__slots__
Attribut ist eine einfache Optimierung in Python, um den von der Klasse benötigten Gesamtspeicher (Attribute) zu definieren und die Speichergröße zu reduzieren. Hier sind die resultierenden Diagramme.Wie bereits erwähnt, macht die verwendete Technik nur einen minimalen Unterschied, und Sie sollten auf eine Weise codieren, die für Sie am besten lesbar ist, oder unter bestimmten Umständen. In diesem Fall ist das Listenverständnis (
map_comprehension
Technik) für beide Arten von Ergänzungen in einem Objekt am schnellsten, insbesondere bei kürzeren Listen.Besuchen Sie diesen Pastebin für die Quelle, aus der das Diagramm und die Daten generiert wurden.
quelle
map
ist es nur dann schneller, wenn die Funktion genauso aufgerufen wird (dh[*map(f, vals)]
vs.[f(x) for x in vals]
). Istlist(map(methodcaller("add"), vals))
also schneller als[methodcaller("add")(x) for x in vals]
.map
ist möglicherweise nicht schneller, wenn das Gegenstück zur Schleife eine andere aufrufende Methode verwendet, mit der ein gewisser Overheadx.add()
vermieden werden kann (z. B. vermeidet dermethodcaller
Overhead des oder des Lambda-Ausdrucks). Für diesen speziellen Testfall[*map(DummyNum.add, vals)]
wäre schneller (weilDummyNum.add(x)
undx.add()
haben im Grunde die gleiche Leistung).list()
Aufrufe sind übrigens etwas langsamer als das Listenverständnis. Für einen fairen Vergleich müssen Sie schreiben[*map(...)]
.list()
Anrufe den Overhead erhöhten. Hätte mehr Zeit damit verbringen sollen, die Antworten durchzulesen. Ich werde diese Tests für einen fairen Vergleich wiederholen, wie vernachlässigbar die Unterschiede auch sein mögen.Ich bin der Meinung, dass der pythonischste Weg darin besteht, anstelle von
map
und ein Listenverständnis zu verwendenfilter
. Der Grund ist, dass das Listenverständnis klarer ist alsmap
undfilter
.Wie Sie sehen, erfordert ein Verständnis keine zusätzlichen
lambda
Ausdrücke alsmap
Bedürfnisse. Darüber hinaus ermöglicht ein Verständnis auch eine einfache Filterung, während eine Filterungmap
erforderlich istfilter
.quelle
Ich habe den Code von @ alex-martelli ausprobiert, aber einige Unstimmigkeiten festgestellt
Die Karte benötigt auch für sehr große Bereiche dieselbe Zeit, während die Verwendung des Listenverständnisses viel Zeit in Anspruch nimmt, wie aus meinem Code hervorgeht. Abgesehen davon, dass ich als "unpythonisch" eingestuft wurde, habe ich keine Leistungsprobleme im Zusammenhang mit der Verwendung der Karte festgestellt.
quelle
map
eine Liste zurückgegeben wird. In Python 3 wirdmap
es träge ausgewertet, sodass beim einfachen Aufrufenmap
keines der neuen Listenelemente berechnet wird, weshalb Sie so kurze Zeiten erhalten.