Liste Verständnis vs Karte
-
12-09-2019 - |
Frage
Gibt es einen Grund mit map()
über Liste Verständnis oder umgekehrt zu bevorzugen? Ist einer von ihnen im Allgemeinen effiziente oder als allgemein mehr pythonic als die anderen?
Lösung
map
kann in einigen Fällen mikroskopisch schneller sein (wenn Sie nicht gerade ein Lambda zum Zweck zu machen, aber die gleiche Funktion in der Karte und eine listcomp verwenden). Listenkomprehensionen schneller kann in anderen Fällen und die meisten (nicht alle) Pythonistas betrachten sie als direkter und klarer.
Ein Beispiel für den kleinen Geschwindigkeitsvorteil der Karte bei der Verwendung genau die gleiche Funktion:
$ python -mtimeit -s'xs=range(10)' 'map(hex, xs)'
100000 loops, best of 3: 4.86 usec per loop
$ python -mtimeit -s'xs=range(10)' '[hex(x) for x in xs]'
100000 loops, best of 3: 5.58 usec per loop
Ein Beispiel, wie Performance-Vergleich wird vollständig umgekehrt, wenn Karte ein Lambda muss:
$ python -mtimeit -s'xs=range(10)' 'map(lambda x: x+2, xs)'
100000 loops, best of 3: 4.24 usec per loop
$ python -mtimeit -s'xs=range(10)' '[x+2 for x in xs]'
100000 loops, best of 3: 2.32 usec per loop
Andere Tipps
Hüllen
- Allgemein Fall : Fast immer werden Sie in einer Liste Verständnis verwenden Python , weil es offensichtlich ist, was Sie Anfänger Programmierer tun Ihren Code lesen . (Dies gilt nicht für andere Sprachen, in denen andere Idiome gelten.) Es wird noch deutlicher, was Sie zu Python-Programmierer tun, da Listenkomprehensionen die de-facto-Standard in Python für Iteration sind; sie sind erwartet .
- Weniger-common Fall : Wenn Sie jedoch bereits eine Funktion definiert , ist es oft sinnvoll
map
zu verwenden, obwohl es unpythonic 'betrachtet wird. Zum Beispiel istmap(sum, myLists)
elegantere / lapidar als[sum(x) for x in myLists]
. Sie gewinnen die Eleganz nicht einen Dummy-Variable (zsum(x) for x...
odersum(_) for _...
odersum(readableName) for readableName...
), die Sie zweimal eingeben müssen, um zu müssen, nur um iterieren. Das gleiche Argument gilt fürfilter
undreduce
und etwas aus demitertools
Modul: Wenn Sie bereits über eine Funktion zur Hand haben, können Sie voran gehen und einige funktionale Programmierung tun. Dies gewinnt Lesbarkeit in einigen Situationen und verliert es in anderen (zum Beispiel Programmieranfängern, mehr Argumente) ... aber die Lesbarkeit des Codes hängt stark von Ihren Kommentar trotzdem. - Fast nie : Sie möchten die
map
Funktion als reine abstrakte Funktion verwenden, während die funktionale Programmierung zu tun, wo Siemap
kartieren odermap
currying oder auf andere Weise profitieren von reden übermap
als eine Funktion. In Haskell beispielsweise eine Schnittstelle Funktorsfmap
verallgemeinert Mapping über jede Datenstruktur genannt. Das ist sehr ungewöhnlich in Python, weil der Python Grammatik zwingt Sie Generator-Stil zu verwenden, um über Iteration zu sprechen; Sie können es nicht leicht verallgemeinern. (Dies ist manchmal gut und manchmal schlecht.) Sie können sich wahrscheinlich kommen mit seltenen Python Beispiele, bei denenmap(f, *lists)
eine vernünftige Sache zu tun. Das nächstgelegene Beispiel ich mit oben kommen kann würdesumEach = partial(map,sum)
, das ist ein Einzeiler, die sehr entspricht in etwa:
def sumEach(myLists):
return [sum(_) for _ in myLists]
- Nur eine
for
-Schleife mit : Sie können auch for-Schleife verwenden natürlich nur. Zwar nicht so elegant aus funktionaler Programmierung Sicht macht manchmal nicht-lokale Variablen Code klarer in imperativen Programmiersprachen wie Python, weil die Menschen zu sehr verwendet werden Code-Lese auf diese Weise. For-Schleifen sind auch, in der Regel, die am effizientesten, wenn Sie nur sind jede komplexe Operation zu tun, die nicht eine Liste wie list-Comprehensions und Karte baut werden (zB Summieren, oder einen Baum zu machen, etc.) optimiert - zumindest effizient in Bezug auf Speicher (nicht unbedingt in Bezug auf die Zeit, wo ich im schlimmsten Fall einen konstanten Faktor erwarten würde, einige seltene pathologische Garbage-collection Schluckauf Sperre).
"Pythonism"
Ich mag nicht das Wort „pythonic“, weil ich finde nicht, dass pythonic in meinen Augen immer elegant. Dennoch map
und filter
und ähnliche Funktionen (wie die sehr nützlich itertools
Modul) sind wahrscheinlich unpythonic betrachten in Bezug auf Stil.
Laziness
Im Hinblick auf die Effizienz, wie die meisten funktionalen Programmierung Konstrukte, MAP CAN LAZY BE, und in der Tat ist in Python faul. Das heißt, Sie können dies tun (in python3 ) und Ihr Computer nicht über genügend Arbeitsspeicher ausgeführt und verlieren alle nicht gespeicherten Daten:
>>> map(str, range(10**100))
<map object at 0x2201d50>
Versuchen Sie das mal mit einer Liste Verständnis zu tun:
>>> [str(n) for n in range(10**100)]
# DO NOT TRY THIS AT HOME OR YOU WILL BE SAD #
Sie beachten Sie, dass Listenkomprehensionen auch von Natur aus faul sind, aber Python hat sich dafür entschieden, sie als nicht-faul zu implementieren . Dennoch tut Python faul Listenkomprehensionen in Form von Generator ausdr unterstützenessions wie folgt:
>>> (str(n) for n in range(10**100))
<generator object <genexpr> at 0xacbdef>
Sie können im Grunde denken Sie an die [...]
Syntax wie in einem Generator Ausdruck der Liste Konstruktor übergeben, wie list(x for x in range(5))
.
Kurz konstruiertes Beispiel
from operator import neg
print({x:x**2 for x in map(neg,range(5))})
print({x:x**2 for x in [-y for y in range(5)]})
print({x:x**2 for x in (-y for y in range(5))})
Liste Comprehensions sind nicht faul, so kann mehr Speicher benötigen (es sei denn, Sie Generator Comprehensions verwenden). Die eckigen Klammern [...]
oft die Dinge offensichtlich, vor allem, wenn sie in einem Gewirr von Klammern. Auf der anderen Seite, manchmal Sie am Ende wie die Eingabe [x for x in...
verbose. Solange Sie Ihre Iterator Variablen kurz halten, sind Listenkomprehensionen in der Regel deutlicher, wenn Sie Ihren Code nicht einrücken kann. Aber man kann immer Ihren Code einrücken.
print(
{x:x**2 for x in (-y for y in range(5))}
)
oder Dinge brechen:
rangeNeg5 = (-y for y in range(5))
print(
{x:x**2 for x in rangeNeg5}
)
Effizienzvergleich für python3
map
ist jetzt faul:
% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=map(f,xs)'
1000000 loops, best of 3: 0.336 usec per loop ^^^^^^^^^
Deshalb, wenn Sie nicht alle Ihre Daten verwenden, oder nicht wissen, vor der Zeit, wie viele Daten Sie benötigen, map
in python3 (und Generator Ausdrücke in python2 oder python3) wird vermieden, ihre Werte bis zum letzten Berechnung Moment notwendig. Normalerweise wird dies in der Regel einen zusätzlichen Overhead aufwiegen von map
verwenden. Der Nachteil ist, dass diese sehr ist in Python beschränkt, im Gegensatz zu den meisten funktionalen Sprachen. Sie diesen Vorteil nur erhalten, wenn Sie Ihre Daten von links nach rechts „um“ zugreifen, da Ausdrücke Python-Generator kann nur die Reihenfolge x[0], x[1], x[2], ...
ausgewertet werden
Lassen Sie uns aber sagen, dass wir haben vorgefertigte Funktion f
wir map
möchten, und wir ignorieren die Faulheit der map
durch sofort zwingt Auswertung mit list(...)
. Wir bekommen einige sehr interessante Ergebnisse:
% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=list(map(f,xs))'
10000 loops, best of 3: 165/124/135 usec per loop ^^^^^^^^^^^^^^^
for list(<map object>)
% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=[f(x) for x in xs]'
10000 loops, best of 3: 181/118/123 usec per loop ^^^^^^^^^^^^^^^^^^
for list(<generator>), probably optimized
% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=list(f(x) for x in xs)'
1000 loops, best of 3: 215/150/150 usec per loop ^^^^^^^^^^^^^^^^^^^^^^
for list(<generator>)
Ergebnisse sind in Form AAA / BBB / CCC, wobei A mit auf einem circa 2010 Intel Workstation mit Python 3 durchgeführt wurde.?.?, Und B und C wurden mit einer circa 2013 AMD-Workstation mit Python ausgeführt 3.2 0,1, mit extrem unterschiedlicher Hardware. Das Ergebnis scheint zu sein, dass Karte und Listenkomprehensionen in der Leistung vergleichbar sind, die am stärksten von anderen zufälligen Faktoren beeinflusst wird. Das einzige, was wir sagen können, scheint, dass, seltsam zu sein, während wir Listenkomprehensionen erwarten eine bessere Leistung [...]
als Generator Ausdrücke (...)
ist map
auch effiziente, dass Generator Ausdrücke (wieder unter der Annahme, dass alle Werte ausgewertet werden / verwendet).
Es ist wichtig zu erkennen, dass diese Tests eine sehr einfache Funktion (die Identitätsfunktion) übernehmen; aber das ist in Ordnung, denn wenn die Funktion kompliziert waren, dann Performance-Overhead zu vernachlässigen wäre im Vergleich zu anderen Faktoren im Programm. (Es kann immer noch interessant sein, mit anderen einfachen Dingen wie f=lambda x:x+x
zu testen)
Wenn Sie beim Lesen Python Montage Mann sind, können Sie das dis
Modul zu sehen, benutzen, wenn das ist eigentlich das, was hinter den Kulissen vor sich geht:
>>> listComp = compile('[f(x) for x in xs]', 'listComp', 'eval')
>>> dis.dis(listComp)
1 0 LOAD_CONST 0 (<code object <listcomp> at 0x2511a48, file "listComp", line 1>)
3 MAKE_FUNCTION 0
6 LOAD_NAME 0 (xs)
9 GET_ITER
10 CALL_FUNCTION 1
13 RETURN_VALUE
>>> listComp.co_consts
(<code object <listcomp> at 0x2511a48, file "listComp", line 1>,)
>>> dis.dis(listComp.co_consts[0])
1 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 18 (to 27)
9 STORE_FAST 1 (x)
12 LOAD_GLOBAL 0 (f)
15 LOAD_FAST 1 (x)
18 CALL_FUNCTION 1
21 LIST_APPEND 2
24 JUMP_ABSOLUTE 6
>> 27 RETURN_VALUE
>>> listComp2 = compile('list(f(x) for x in xs)', 'listComp2', 'eval')
>>> dis.dis(listComp2)
1 0 LOAD_NAME 0 (list)
3 LOAD_CONST 0 (<code object <genexpr> at 0x255bc68, file "listComp2", line 1>)
6 MAKE_FUNCTION 0
9 LOAD_NAME 1 (xs)
12 GET_ITER
13 CALL_FUNCTION 1
16 CALL_FUNCTION 1
19 RETURN_VALUE
>>> listComp2.co_consts
(<code object <genexpr> at 0x255bc68, file "listComp2", line 1>,)
>>> dis.dis(listComp2.co_consts[0])
1 0 LOAD_FAST 0 (.0)
>> 3 FOR_ITER 17 (to 23)
6 STORE_FAST 1 (x)
9 LOAD_GLOBAL 0 (f)
12 LOAD_FAST 1 (x)
15 CALL_FUNCTION 1
18 YIELD_VALUE
19 POP_TOP
20 JUMP_ABSOLUTE 3
>> 23 LOAD_CONST 0 (None)
26 RETURN_VALUE
>>> evalledMap = compile('list(map(f,xs))', 'evalledMap', 'eval')
>>> dis.dis(evalledMap)
1 0 LOAD_NAME 0 (list)
3 LOAD_NAME 1 (map)
6 LOAD_NAME 2 (f)
9 LOAD_NAME 3 (xs)
12 CALL_FUNCTION 2
15 CALL_FUNCTION 1
18 RETURN_VALUE
Es scheint, es besser ist, [...]
Syntax als list(...)
zu verwenden. Leider ist die map
Klasse ein bisschen undurchsichtig Demontage ist, aber wir können mit unserem Geschwindigkeitstest durch machen.
Python 2: Sie sollten map
und filter
statt Listenkomprehensionen verwenden
.
Ein Ziel Grund, warum Sie sie bevorzugen sollten, auch wenn sie nicht „Pythonic“ sind, ist dies:
Sie benötigen Funktionen / Lambda-Ausdrücke als Argumente, die Einführung eines neuen Bereichs .
Ich habe durch diese mehr als einmal gebissen gotten:
for x, y in somePoints:
# (several lines of code here)
squared = [x ** 2 for x in numbers]
# Oops, x was silently overwritten!
aber wenn stattdessen hatte ich gesagt:
for x, y in somePoints:
# (several lines of code here)
squared = map(lambda x: x ** 2, numbers)
dann würde ich alles in Ordnung gewesen.
Man könnte sagen, ich wurde mit dem gleichen Variablennamen im gleichen Umfang albern.
ich war es nicht. Der Code war ursprünglich in Ordnung - die beide x
s war nicht im gleichen Umfang
.
Erst nach I bewegte der innere Block zu einem anderen Abschnitt des Codes, der das Problem kam. (Sprich: Problem bei der Wartung nicht Entwicklung), und ich habe es nicht erwartet
Ja, , wenn Sie nie diesen Fehler dann Listenkomprehensionen sind eleganter.
Aber aus eigener Erfahrung (und nicht sehen andere den gleichen Fehler machen) Ich habe es oft genug geschehen gesehen, dass ich denke, es lohnt sich nicht, die Schmerzen, die Sie haben, um durch zu gehen, wenn diese Fehler in Ihren Code kriechen.
Fazit:
Mit map
und filter
. Sie verhindern, dass subtile schwer zu diagnostizieren Umfang bezogene Fehler.
Side Hinweis:
Vergessen Sie nicht, mit imap
und ifilter
(in itertools
) zu prüfen, ob sie für Ihre Situation geeignet sind!
Eigentlich map
und Listenkomprehensionen verhalten sich ganz anders in der Python 3 Sprache. Werfen Sie einen Blick auf die folgende Python-3-Programm:
def square(x):
return x*x
squares = map(square, [1, 2, 3])
print(list(squares))
print(list(squares))
Sie können erwarten, dass es die Zeile drucken "[1, 4, 9]" zweimal, sondern drucken "[1, 4, 9]", gefolgt von "[]". Das erste Mal, wenn Sie auf squares
sehen es als eine Folge von drei Elementen zu verhalten scheint, aber das zweite Mal als leeres.
In der Python 2 Sprache map
gibt eine einfache alte Liste, wie Listenkomprehensionen in beiden Sprachen zu tun. Der springende Punkt ist, dass der Rückgabewert von map
in Python 3 (und imap
in Python 2) ist keine Liste - es ist ein Iterator
Die Elemente verbraucht werden, wenn Sie über einen Iterator iteriert im Gegensatz zu, wenn Sie über eine Liste durchlaufen. Aus diesem Grunde squares
in der letzten print(list(squares))
Zeile leer aussieht.
Um es zusammenzufassen:
- Wenn Sie mit Iteratoren Umgang Sie haben sich daran zu erinnern, dass sie Stateful und dass sie mutieren, wie Sie sie durchqueren.
- Listen sind berechenbarer, da sie nur dann ändern, wenn Sie sich ausdrücklich mutieren; sie sind Container .
- Und ein Bonus: Zahlen, Strings und Tupel sind noch vorhersehbar, da sie nicht ändern kann; sie sind Werte .
Ich finde Listenkomprehensionen des Regel ausdruck ist, was ich versuche als map
zu tun - sie beide es getan, aber die erstere speichern die mentale Belastung zu verstehen versuchen, was ein komplexer lambda
Ausdruck sein könnte
Es gibt auch ein Interview irgendwo da draußen (ich kann es nicht finden Stegreif), wo Guido lambda
s und die funktionalen Funktionen wie die Sache zählt er am meisten bereut in Python zu akzeptieren, so dass Sie das Argument machen könnten, dass sie un- sind Pythonic durch die.
Hier ist ein möglicher Fall:
map(lambda op1,op2: op1*op2, list1, list2)
Vergleich:
[op1*op2 for op1,op2 in zip(list1,list2)]
ich die Zip bin zu raten () ist ein unglücklicher und unnötiger Aufwand Sie, wenn Sie über die Verwendung Listenkomprehensionen statt die Karte bestehen frönen müssen. Wäre toll, wenn jemand dies stellt klar, ob bejahend oder verneinend.
Wenn Sie auf Schreiben eines beliebigen asynchronen, parallele oder verteilte Code planen, werden Sie wahrscheinlich map
über eine Liste Verständnis bevorzugen - wie die meisten asynchrone, parallele oder verteilte Pakete bieten eine map
Funktion Pythons map
zu überlasten. Dann wird durch die entsprechende map
Funktion auf den Rest des Codes vorbei, können Sie nicht haben, um Ihre Original-Seriencode zu ändern, um sie parallel laufen zu müssen (usw.).
So, da Python 3, map()
ein Iterator ist, Sie müssen im Auge behalten, was tun Sie brauchen:. einen Iterator oder list
Objekt
Wie bereits @AlexMartelli erwähnt ist map()
schneller als Liste Verständnis nur, wenn Sie nicht lambda
Funktion verwenden.
Ich werde Ihnen einige Zeitvergleiche präsentieren.
Python 3.5.2 und CPython
Ich habe verwendet Jupiter Notebook und insbesondere %timeit
Magie Befehl Built-in
Messungen : s == 1000 ms == 1000 * 1000 & mgr; s = 1000 * 1000 * 1000 ns
Setup:
x_list = [(i, i+1, i+2, i*2, i-9) for i in range(1000)]
i_list = list(range(1000))
Built-in-Funktion:
%timeit map(sum, x_list) # creating iterator object
# Output: The slowest run took 9.91 times longer than the fastest.
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 277 ns per loop
%timeit list(map(sum, x_list)) # creating list with map
# Output: 1000 loops, best of 3: 214 µs per loop
%timeit [sum(x) for x in x_list] # creating list with list comprehension
# Output: 1000 loops, best of 3: 290 µs per loop
lambda
Funktion:
%timeit map(lambda i: i+1, i_list)
# Output: The slowest run took 8.64 times longer than the fastest.
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 325 ns per loop
%timeit list(map(lambda i: i+1, i_list))
# Output: 1000 loops, best of 3: 183 µs per loop
%timeit [i+1 for i in i_list]
# Output: 10000 loops, best of 3: 84.2 µs per loop
Es gibt auch so etwas wie Generator Ausdruck finden Sie unter PEP- 0289 . Also dachte ich, es wäre nützlich, es zum Vergleich hinzufügen
%timeit (sum(i) for i in x_list)
# Output: The slowest run took 6.66 times longer than the fastest.
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 495 ns per loop
%timeit list((sum(x) for x in x_list))
# Output: 1000 loops, best of 3: 319 µs per loop
%timeit (i+1 for i in i_list)
# Output: The slowest run took 6.83 times longer than the fastest.
# This could mean that an intermediate result is being cached.
# 1000000 loops, best of 3: 506 ns per loop
%timeit list((i+1 for i in i_list))
# Output: 10000 loops, best of 3: 125 µs per loop
Sie müssen list
Objekt:
Verwenden Liste Verständnis, wenn es benutzerdefinierte Funktion, Gebrauch list(map())
wenn es eingebaute Funktion ist
Sie haben nicht list
Objekt benötigen, brauchen Sie nur iterable ein:
Verwenden Sie immer map()
!
Ich denke, dass die meisten Pythonic Art und Weise ist eine Liste Verständnis statt map
und filter
zu verwenden. Der Grund dafür ist, dass Listenkomprehensionen sind klarer als map
und filter
.
In [1]: odd_cubes = [x ** 3 for x in range(10) if x % 2 == 1] # using a list comprehension
In [2]: odd_cubes_alt = list(map(lambda x: x ** 3, filter(lambda x: x % 2 == 1, range(10)))) # using map and filter
In [3]: odd_cubes == odd_cubes_alt
Out[3]: True
Wie Sie ein sehen, die ein Verständnis nicht zusätzliche lambda
erfordert Ausdrücke wie map
Bedürfnisse. Darüber hinaus auch ein Verständnis erlaubt das Filtern leicht, während map
filter
Filterung zu ermöglichen, erfordert.
lief ich einen schnellen Test zu vergleichen drei Methoden für die Methode eines Objekts aufgerufen wird. Die Zeitdifferenz, in diesem Fall vernachlässigbar und ist eine Frage der Funktion in Frage (siehe @ Alex Martelli Antwort ) . Hier habe ich bei den folgenden Methoden angesehen:
# map_lambda
list(map(lambda x: x.add(), vals))
# map_operator
from operator import methodcaller
list(map(methodcaller("add"), vals))
# map_comprehension
[x.add() for x in vals]
ich auf Listen sah (in der Variablen vals
gespeichert) der beiden ganzen Zahlen (Python int
) und Gleitkommazahlen (Python float
) für Listengrößen zu erhöhen. Die folgende Dummy-Klasse DummyNum
gilt:
class DummyNum(object):
"""Dummy class"""
__slots__ = 'n',
def __init__(self, n):
self.n = n
def add(self):
self.n += 5
Insbesondere die add
Methode. Das __slots__
Attribut ist eine einfache Optimierung in Python die Gesamt Speicher durch die Klasse (Attribute) benötigt, um zu definieren, die Verringerung der Speichergröße.
Hier sind die resultierenden Plots.
Wie bereits erwähnt, verwendet die Technik macht einen minimalen Unterschied, und Sie sollten in einer Weise kodieren, dass die meisten lesbar zu Ihnen, oder in dem besonderen Umstand. In diesem Fall ist die Liste Verständnis (map_comprehension
Technik) am schnellsten für beiden Arten von Zusätzen in einem Objekt, insbesondere bei kürzeren Listen.
Besuchen Sie diese Pastebin für die Quelle verwendet, um die Handlung und Daten zu erzeugen.