Listenverständnis vs Karte

732

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?

TimothyAWiseman
quelle
8
Beachten Sie, dass PyLint warnt, wenn Sie anstelle des Listenverständnisses eine Karte verwenden (siehe Meldung W0141) .
Lumbric
2
@ Lumbric, ich bin nicht sicher, aber es funktioniert nur, wenn Lambda in der Karte verwendet wird.
0xc0de

Antworten:

660

mapkann 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:

$ 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 dafür, wie der Leistungsvergleich vollständig umgekehrt wird, wenn die Karte ein Lambda benötigt:

$ 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
Alex Martelli
quelle
39
Ja, in der Tat empfiehlt unser interner Python-Styleguide bei der Arbeit ausdrücklich Listcomps gegen Map und Filter (nicht einmal die winzige, aber messbare Map zur Leistungsverbesserung kann in einigen Fällen helfen ;-).
Alex Martelli
46
Nicht auf Alex 'unendlichen Stilpunkten zu kibashen, aber manchmal scheint mir die Karte leichter zu lesen zu sein: data = map (str, some_list_of_objects). Einige andere ... operator.attrgetter, operator.itemgetter usw.
Gregg Lind
57
map(operator.attrgetter('foo'), objs)leichter zu lesen als [o.foo for o in objs]?!
Alex Martelli
52
@Alex: Ich ziehe es vor, keine unnötigen Namen wie ohier einzuführen , und Ihre Beispiele zeigen, warum.
Reid Barton
29
Ich denke, dass @GreggLind mit seinem str()Beispiel einen Punkt hat .
Eric O Lebigot
474

Fälle

  • Häufiger Fall : Fast immer möchten Sie ein Listenverständnis in Python verwenden, da es für Anfänger, die Ihren Code lesen, offensichtlicher ist, was Sie tun. (Dies gilt nicht für andere Sprachen, in denen möglicherweise andere Redewendungen gelten.) Es wird noch offensichtlicher, was Sie Python-Programmierern antun, da Listenverständnisse der De-facto-Standard in Python für die Iteration sind. sie werden erwartet .
  • Seltener Fall : Wenn Sie jedoch bereits eine Funktion definiert haben , ist die Verwendung häufig sinnvoll map, obwohl sie als "unpythonisch" angesehen wird. Zum Beispiel map(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...oder sum(_) for _...oder sum(readableName) for readableName...) erstellen zu müssen, die Sie zweimal eingeben müssen, nur um zu iterieren. Das gleiche Argument gilt für filterund reduceund alles aus dem itertoolsModul: 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.
  • Fast nie : Möglicherweise möchten Sie die mapFunktion als reine abstrakte Funktion verwenden, während Sie eine funktionale Programmierung durchführen, bei der Sie Mapping mapoder Currying durchführen mapoder auf andere Weise davon profitieren, über mapeine Funktion zu sprechen . In Haskell beispielsweise fmapverallgemeinert 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 denen map(f, *lists)es sinnvoll ist, dies zu tun. Das nächste Beispiel, das ich finden kann sumEach = partial(map,sum), ist ein Einzeiler, der in etwa gleichwertig ist mit:

def sumEach(myLists):
    return [sum(_) for _ in myLists]
  • Nur eine- forSchleife 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 mapund filterund ähnliche Funktionen (wie das sehr nützlich itertoolsModul) 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:

>>> map(str, range(10**100))
<map object at 0x2201d50>

Versuchen Sie dies mit einem Listenverständnis:

>>> [str(n) for n in range(10**100)]
# DO NOT TRY THIS AT HOME OR YOU WILL BE SAD #

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:

>>> (str(n) for n in range(10**100))
<generator object <genexpr> at 0xacbdef>

Sie können sich die [...]Syntax grundsätzlich als Übergabe eines Generatorausdrucks an den Listenkonstruktor vorstellen list(x for x in range(5)).

Kurzes erfundenes 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))})

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.

print(
    {x:x**2 for x in (-y for y in range(5))}
)

oder aufbrechen:

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            ^^^^^^^^^

Wenn Sie daher nicht alle Ihre Daten verwenden oder nicht mapim 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 Verwendung map. 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önnen x[0], x[1], x[2], ....

Nehmen wir jedoch an, wir haben eine vorgefertigte Funktion, die fwir möchten map, und wir ignorieren die Faulheit, mapindem wir sofort die Bewertung mit erzwingen list(...). Wir erhalten 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>)

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 (...), mapAUCH 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 disModuls feststellen , ob dies tatsächlich hinter den Kulissen geschieht:

>>> 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 besser zu sein, [...]Syntax zu verwenden als list(...). Leider ist die mapKlasse für die Demontage etwas undurchsichtig, aber wir können mit unserem Geschwindigkeitstest dafür sorgen.

Ninjagecko
quelle
5
"Das sehr nützliche itertools-Modul wird in Bezug auf den Stil wahrscheinlich als unpythonisch angesehen." Hmm. Ich mag den Begriff "Pythonic" auch nicht, daher ist es mir in gewisser Weise egal, was er bedeutet, aber ich denke nicht, dass es fair für diejenigen ist, die ihn verwenden, dies gemäß den "Pythonicness" -Einbauten mapund zu sagen filterzusammen mit Standardbibliothek itertoolssind 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 ;-)
Steve Jessop
4
@SteveJessop: Eigentlich dachte Guido, Drop map/ wäre filtereine großartige Idee für Python 3 , und nur eine Rebellion anderer Pythonisten hielt sie im eingebauten Namespace (während sie reduceverschoben wurden functools). Ich persönlich bin anderer Meinung ( mapund bin filtermit vordefinierten, insbesondere eingebauten Funktionen einverstanden , benutze sie nur nie, wenn eine lambdabenötigt wird), aber GvR nennt sie seit Jahren grundsätzlich nicht Pythonic.
ShadowRanger
@ShadowRanger: stimmt, aber hatte GvR jemals vor, es zu entfernen itertools? Der Teil, den ich aus dieser Antwort zitiere, ist die Hauptbehauptung, die mich verwirrt. Ich weiß nicht, ob in seiner idealen Welt, mapund filterwürde zu itertools(oder functools) oder ganz gehen, aber was auch immer der Fall ist, wenn man sagt, dass itertoolsdas 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".
Steve Jessop
2
@SteveJessop: Ich habe nur map/ angesprochen filter, nicht itertools. Funktionale Programmierung ist perfekt Pythonic ( itertools, functoolsund operatorwurden alle speziell mit der funktionalen Programmierung konzipiert, und ich verwende funktionelle Idiome in Python die ganze Zeit), und itertoolsbietet Funktionen , die ein Schmerz selbst zu implementieren sein würde, es ist speziell mapund filterredundant mit Generator Ausdrücke das ließ Guido sie hassen. itertoolswar schon immer in Ordnung.
ShadowRanger
1
Ich könnte diese Antwort favorisieren, wenn es einen Weg gäbe. Gut erklärt.
NelsonGon
95

Python 2: Sie sollten mapund filteranstelle 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:

for x, y in somePoints:
    # (several lines of code here)
    squared = [x ** 2 for x in numbers]
    # Oops, x was silently overwritten!

aber wenn ich stattdessen gesagt hätte:

for x, y in somePoints:
    # (several lines of code here)
    squared = map(lambda x: x ** 2, numbers)

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 xs 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 mapund filter. Sie verhindern subtile, schwer zu diagnostizierende Fehler im Zusammenhang mit dem Bereich.

Randnotiz:

Vergessen Sie nicht, imapund ifilter(in itertools) in Betracht zu ziehen, ob sie für Ihre Situation geeignet sind!

user541686
quelle
7
Vielen Dank für den Hinweis. Mir war nicht ausdrücklich in den Sinn gekommen, dass das Listenverständnis im gleichen Umfang liegt und ein Problem sein könnte. Vor diesem Hintergrund denke ich, dass einige der anderen Antworten deutlich machen, dass das Listenverständnis die meiste Zeit der Standardansatz sein sollte, aber dass dies etwas ist, an das man sich erinnern sollte. Dies ist auch eine gute allgemeine Erinnerung daran, Funktionen (und damit den Umfang) klein zu halten, gründliche Unit-Tests durchzuführen und Assert-Anweisungen zu verwenden.
TimothyAWiseman
13
@wim: Hier ging es nur um Python 2, obwohl es für Python 3 gilt, wenn Sie abwärtskompatibel bleiben möchten. Ich wusste davon und hatte Python schon eine Weile benutzt (ja, mehr als nur ein paar Monate), und doch ist es mir passiert. Ich habe gesehen, wie andere, die schlauer sind als ich, in dieselbe Falle geraten. Wenn Sie so intelligent und / oder erfahren sind, dass dies für Sie kein Problem darstellt, freue ich mich für Sie. Ich glaube nicht, dass die meisten Menschen wie Sie sind. Wenn dies der Fall wäre, gäbe es keinen solchen Drang, das
Problem
12
Es tut mir leid, aber Sie haben dies Ende 2012 geschrieben, lange nachdem Python 3 auf den Markt gekommen ist, und die Antwort lautet, als würden Sie einen ansonsten unpopulären Stil der Python-Codierung empfehlen, nur weil Sie beim Schneiden und Schneiden von einem Fehler gebissen wurden Code einfügen. Ich habe nie behauptet, klug oder erfahren zu sein, ich stimme einfach nicht zu, dass die kühne Behauptung durch Ihre Gründe gerechtfertigt ist.
wim
8
@wim: Huh? Python 2 wird immer noch an vielen Orten verwendet. Die Tatsache, dass Python 3 existiert, ändert daran nichts. Und wenn Sie sagen "es ist nicht gerade ein subtiler Fehler für jemanden, der Python länger als ein paar Monate verwendet hat", bedeutet dieser Satz wörtlich "dies betrifft nur unerfahrene Entwickler" (eindeutig nicht Sie). Und fürs Protokoll: Sie haben die Antwort eindeutig nicht gelesen, weil ich fett gesagt habe, dass ich Code verschoben und nicht kopiert habe. Copy-Paste-Fehler sind sprachübergreifend ziemlich einheitlich. Diese Art von Fehler ist aufgrund seines Umfangs für Python einzigartiger. Es ist subtiler und leichter zu vergessen und zu verpassen.
user541686
3
Es ist immer noch kein logischer Grund für den Wechsel zu mapund / oder filter. Wenn überhaupt, ist die direkteste und logischste Übersetzung, um Ihr Problem zu vermeiden, nicht map(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.
wim
46

Tatsächlich mapverhalten sich Listenverständnisse in der Python 3-Sprache ganz anders. Schauen Sie sich das folgende Python 3-Programm an:

def square(x):
    return x*x
squares = map(square, [1, 2, 3])
print(list(squares))
print(list(squares))

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 mapeine einfache alte Liste zurückgegeben, genau wie es das Listenverständnis in beiden Sprachen tut. Der springende Punkt ist, dass der Rückgabewert von mapin Python 3 (und imapin 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 squaressieht es in der letzten print(list(squares))Zeile leer aus .

Zusammenfassen:

  • Wenn Sie mit Iteratoren arbeiten, müssen Sie sich daran erinnern, dass sie zustandsbehaftet sind und mutieren, wenn Sie sie durchlaufen.
  • Listen sind vorhersehbarer, da sie sich nur ändern, wenn Sie sie explizit mutieren. Sie sind Container .
  • Und ein Bonus: Zahlen, Zeichenfolgen und Tupel sind noch vorhersehbarer, da sie sich überhaupt nicht ändern können. Sie sind Werte .
raek
quelle
Dies ist wahrscheinlich das beste Argument für das Listenverständnis. Python Map ist nicht die funktionale Map, sondern das verkrüppelte rothaarige Stiefkind einer funktionalen Implementierung. Sehr traurig, weil ich Verständnis wirklich nicht mag.
Semiomant
@semiomant Ich würde sagen, Lazy Map (wie in Python3) ist "funktionaler" als eifrige Map (wie in Python2). Zum Beispiel ist die Karte in Haskell faul (nun, alles in Haskell ist faul ...). Auf jeden Fall ist Lazy Map besser für die Verkettung von Maps geeignet. Wenn Sie eine Map auf eine Map angewendet haben, die auf die Map angewendet wird, haben Sie eine Liste für jeden Zwischen-Map-Aufruf in Python2, während Sie in Python3 nur eine resultierende Liste haben, sodass der Speicher effizienter ist .
MnZrK
Ich denke, ich möchte mapeine Datenstruktur erzeugen, keinen Iterator. Aber vielleicht sind faule Iteratoren einfacher als faule Datenstrukturen. Denkanstöße. Danke @MnZrK
semiomant
Sie möchten sagen, dass map einen iterablen und keinen Iterator zurückgibt.
user541686
16

Ich 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 komplexer lambdaAusdruck sein könnte.

Es gibt auch irgendwo ein Interview (ich kann es nicht ohne weiteres finden), in dem Guido lambdas 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.

Dan
quelle
9
Ja, seufz, aber Guidos ursprüngliche Absicht, Lambda in Python 3 vollständig zu entfernen, hat eine Flut von Lobbying gegen ihn ausgelöst, also hat er trotz meiner starken Unterstützung darauf zurückgegriffen - na ja, ich denke, Lambda ist in vielen EINFACHEN Fällen einfach zu praktisch , der einzige Das Problem ist, wenn es die Grenzen von SIMPLE überschreitet oder einem Namen zugewiesen wird (in letzterem Fall handelt es sich um ein dummes, humpelndes Duplikat von def! -).
Alex Martelli
1
Das Interview, über das Sie nachdenken, ist das folgende: amk.ca/python/writing/gvr-interview , wo Guido sagt: "Manchmal habe ich Beiträge zu schnell angenommen und später festgestellt, dass es ein Fehler war. Ein Beispiel wäre Einige der funktionalen Programmierfunktionen, wie z. B. Lambda-Funktionen. Lambda ist ein Schlüsselwort, mit dem Sie eine kleine anonyme Funktion erstellen können. Integrierte Funktionen wie Zuordnen, Filtern und Reduzieren führen eine Funktion über einen Sequenztyp wie eine Liste aus. ""
J. Taylor
3
@Alex, ich habe nicht Ihre jahrelange Erfahrung, aber ich habe weitaus kompliziertere Listenverständnisse gesehen als Lambdas. Natürlich ist der Missbrauch von Sprachmerkmalen immer eine schwierige Versuchung, Widerstand zu leisten. Es ist interessant, dass Listenverständnisse (empirisch) anfälliger für Missbrauch sind als Lambdas, obwohl ich nicht sicher bin, warum dies der Fall sein sollte. Ich werde auch darauf hinweisen, dass "humpeln" nicht immer eine schlechte Sache ist. Das Reduzieren des Umfangs von "Dingen, die diese Zeile möglicherweise tut" kann es dem Leser manchmal leichter machen. Zum Beispiel ist das constSchlüsselwort in C ++ ein großer Triumph in dieser Richtung.
Stuart Berg
> guido. Dies ist ein weiterer Beweis dafür, dass Guido verrückt ist. Natürlich lambdawurden sie so lahm gemacht (keine Aussagen ..), dass sie schwierig zu bedienen und sowieso begrenzt sind.
Javadba
16

Hier ist ein möglicher Fall:

map(lambda op1,op2: op1*op2, list1, list2)

gegen:

[op1*op2 for op1,op2 in zip(list1,list2)]

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.

Andz
quelle
"[op1 * op2 von op1, op2 in zip (list1, list2)]" | s / form / for / Und eine äquivalente Liste ohne Zip: (weniger lesbar) [Liste1 [i] * Liste2 [i] für i in Reichweite (len (Liste1))]
schwach
2
Sollte in Ihrem zweiten Code-Zitat, @andz, und auch in @ schwachen Kommentar "für" nicht "von" sein. Ich dachte, ich hätte einen neuen syntaktischen Ansatz für das Listenverständnis entdeckt ... Verdammt.
Physikmichael
4
Um einen sehr späten Kommentar hinzuzufügen, können Sie zipfaul machen, indem Sieitertools.izip
Tacaswell
5
Ich glaube ich bevorzuge es immer noch map(operator.mul, list1, list2). Auf diesen sehr einfachen Ausdrücken auf der linken Seite wird das Verständnis ungeschickt.
Yann Vernier
1
Ich hatte nicht bemerkt, dass die Karte mehrere Iterables als Eingaben für ihre Funktion verwenden und somit ein Zip vermeiden kann.
Bli
16

Wenn Sie asynchronen, parallelen oder verteilten Code schreiben möchten, ziehen Sie wahrscheinlich mapein Listenverständnis vor - da die meisten asynchronen, parallelen oder verteilten Pakete eine mapFunktion zum Überladen von Pythons bieten map. Wenn Sie dann die entsprechende mapFunktion an den Rest Ihres Codes übergeben, müssen Sie möglicherweise nicht Ihren ursprünglichen Seriencode ändern, damit er parallel ausgeführt wird (usw.).

Mike McKerns
quelle
9

Da Python 3 map()ein Iterator ist, müssen Sie bedenken, was Sie benötigen: einen Iterator oder ein listObjekt.

Wie bereits erwähnt , map()ist @AlexMartelli nur dann schneller als das Listenverständnis, wenn Sie keine lambdaFunktion verwenden.

Ich werde Ihnen einige Zeitvergleiche vorstellen.

Python 3.5.2 und CPython
Ich habe Jupiter-Notebook und insbesondere den %timeiteingebauten magischen Befehl verwendet.
Messungen : s == 1000 ms == 1000 * 1000 µs = 1000 * 1000 * 1000 ns

Installieren:

x_list = [(i, i+1, i+2, i*2, i-9) for i in range(1000)]
i_list = list(range(1000))

Eingebaute 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 einen Generatorausdruck, siehe PEP-0289 . Also dachte ich, es wäre nützlich, es zum Vergleich hinzuzufü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 benötigen listObjekt:

Verwenden Sie das Listenverständnis, wenn es sich um eine benutzerdefinierte Funktion handelt, und verwenden Sie es, list(map())wenn eine integrierte Funktion vorhanden ist

Sie brauchen kein listObjekt, Sie brauchen nur ein iterierbares:

Immer benutzen map()!

vishes_shell
quelle
1

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:

# 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 habe mir Listen (in der Variablen gespeichert vals) von Ganzzahlen (Python int) und Gleitkommazahlen (Python float) angesehen, um die Listengröße zu erhöhen. Die folgende Dummy-Klasse DummyNumwird berücksichtigt:

class DummyNum(object):
    """Dummy class"""
    __slots__ = 'n',

    def __init__(self, n):
        self.n = n

    def add(self):
        self.n += 5

Insbesondere die addMethode. 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.

Leistung beim Zuordnen von Python-Objektmethoden

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_comprehensionTechnik) 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.

craymichael
quelle
1
Wie bereits in anderen Antworten erläutert, mapist es nur dann schneller, wenn die Funktion genauso aufgerufen wird (dh [*map(f, vals)]vs. [f(x) for x in vals]). Ist list(map(methodcaller("add"), vals))also schneller als [methodcaller("add")(x) for x in vals]. mapist möglicherweise nicht schneller, wenn das Gegenstück zur Schleife eine andere aufrufende Methode verwendet, mit der ein gewisser Overhead x.add()vermieden werden kann (z. B. vermeidet der methodcallerOverhead des oder des Lambda-Ausdrucks). Für diesen speziellen Testfall [*map(DummyNum.add, vals)]wäre schneller (weil DummyNum.add(x)und x.add()haben im Grunde die gleiche Leistung).
GZ0
1
Explizite list()Aufrufe sind übrigens etwas langsamer als das Listenverständnis. Für einen fairen Vergleich müssen Sie schreiben [*map(...)].
GZ0
@ GZ0 danke für das tolle Feedback! Alles macht Sinn, und ich war mir nicht bewusst, dass 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.
Craymichael
0

Ich bin der Meinung, dass der pythonischste Weg darin besteht, anstelle von mapund ein Listenverständnis zu verwenden filter. Der Grund ist, dass das Listenverständnis klarer ist als mapund 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 sehen, erfordert ein Verständnis keine zusätzlichen lambdaAusdrücke als mapBedürfnisse. Darüber hinaus ermöglicht ein Verständnis auch eine einfache Filterung, während eine Filterung maperforderlich ist filter.

lmiguelvargasf
quelle
0

Ich habe den Code von @ alex-martelli ausprobiert, aber einige Unstimmigkeiten festgestellt

python -mtimeit -s "xs=range(123456)" "map(hex, xs)"
1000000 loops, best of 5: 218 nsec per loop
python -mtimeit -s "xs=range(123456)" "[hex(x) for x in xs]"
10 loops, best of 5: 19.4 msec per loop

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.

Mohit Raj
quelle
3
Dies ist eine sehr alte Frage, und die Antwort, auf die Sie sich beziehen, wurde sehr wahrscheinlich in Bezug auf Python 2 geschrieben, wo mapeine Liste zurückgegeben wird. In Python 3 wird mapes träge ausgewertet, sodass beim einfachen Aufrufen mapkeines der neuen Listenelemente berechnet wird, weshalb Sie so kurze Zeiten erhalten.
Kaya3
Ich denke, Sie verwenden Python 3.x Als ich diese Frage stellte, war Python 3 erst kürzlich veröffentlicht worden und Python 2.x war der Standard.
TimothyAWiseman