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?

War es hilfreich?

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 ist map(sum, myLists) elegantere / lapidar als [sum(x) for x in myLists]. Sie gewinnen die Eleganz nicht einen Dummy-Variable (z sum(x) for x... oder sum(_) for _... oder sum(readableName) for readableName...), die Sie zweimal eingeben müssen, um zu müssen, nur um iterieren. Das gleiche Argument gilt für filter und reduce und etwas aus dem itertools 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 Sie map kartieren oder map currying oder auf andere Weise profitieren von reden über map als eine Funktion. In Haskell beispielsweise eine Schnittstelle Funktors fmap 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 denen map(f, *lists) eine vernünftige Sache zu tun. Das nächstgelegene Beispiel ich mit oben kommen kann würde sumEach = 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 xs 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 lambdas 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.

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