Comprensione elenco vs mappa


733

C'è un motivo per preferire l'uso della map()comprensione over list o viceversa? Uno dei due è generalmente più efficiente o generalmente considerato più pitonico dell'altro?


8
Si noti che PyLint avverte se si utilizza map anziché la comprensione dell'elenco, vedere il messaggio W0141 .
lumbric

2
@lumbric, non ne sono sicuro ma lo fa solo se lambda è usato nella mappa.
0xc0de,

Risposte:


662

mappuò essere microscopicamente più veloce in alcuni casi (quando NON stai realizzando un lambda per lo scopo, ma usando la stessa funzione in map e in un listcomp). La comprensione dell'elenco può essere più rapida in altri casi e la maggior parte (non tutti) dei pitonisti li considera più diretti e chiari.

Un esempio del piccolo vantaggio di velocità della mappa quando si utilizza esattamente la stessa funzione:

$ 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

Un esempio di come il confronto delle prestazioni viene completamente invertito quando la mappa necessita di una lambda:

$ 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

39
Sì, in effetti la nostra guida interna allo stile di Python al lavoro raccomanda esplicitamente elenchi contro mappa e filtro (nemmeno menzionando la piccola ma misurabile mappa di miglioramento delle prestazioni può dare in alcuni casi ;-).
Alex Martelli,

46
Non kibash sugli infiniti punti di stile di Alex, ma a volte la mappa mi sembra più facile da leggere: data = map (str, some_list_of_objects). Alcuni altri ... operator.attrgetter, operator.itemgetter, ecc.
Gregg Lind

57
map(operator.attrgetter('foo'), objs)più facile da leggere di [o.foo for o in objs]?!
Alex Martelli,

52
@Alex: preferisco non introdurre nomi non necessari, come oqui, e i tuoi esempi mostrano perché.
Reid Barton,

29
Penso che @GreggLind abbia ragione, con il suo str()esempio, però.
Eric O Lebigot,

474

casi

  • Caso comune : quasi sempre, vorrai usare una comprensione dell'elenco in Python perché sarà più ovvio quello che stai facendo ai programmatori principianti che leggono il tuo codice. (Questo non si applica ad altre lingue, dove potrebbero essere applicati altri modi di dire.) Sarà persino più ovvio ciò che stai facendo ai programmatori di Python, poiché le comprensioni degli elenchi sono lo standard di fatto in Python per l'iterazione; sono attesi .
  • Caso meno comune : tuttavia, se si dispone già di una funzione definita , è spesso ragionevole utilizzarla map, sebbene sia considerata "non pitonica". Ad esempio, map(sum, myLists)è più elegante / conciso di [sum(x) for x in myLists]. Si guadagna l'eleganza di non dover recuperare una variabile dummy (ad esempio, sum(x) for x...o sum(_) for _...o sum(readableName) for readableName...), che è necessario digitare due volte, solo per iterare. Lo stesso argomento vale per filtere reducequalsiasi cosa del itertoolsmodulo: se hai già una funzione a portata di mano, potresti andare avanti e fare un po 'di programmazione funzionale. Ciò aumenta la leggibilità in alcune situazioni e la perde in altre (ad es. Programmatori principianti, argomenti multipli) ... ma la leggibilità del codice dipende in ogni caso dai commenti.
  • Quasi mai : potresti voler usare la mapfunzione come una pura funzione astratta mentre esegui una programmazione funzionale, dove stai mappando mapo curry map, o altrimenti trarrai vantaggio dal parlare mapcome una funzione. In Haskell, ad esempio, un'interfaccia di funzione chiamata fmapgeneralizza il mapping su qualsiasi struttura di dati. Questo è molto raro in Python perché la grammatica di Python ti costringe a usare lo stile del generatore per parlare di iterazione; non puoi generalizzare facilmente. (Questo a volte è buono e talvolta cattivo.) Probabilmente puoi trovare rari esempi di pitone dove map(f, *lists)è una cosa ragionevole da fare. L'esempio più vicino che posso trovare sarebbe sumEach = partial(map,sum), che è un one-liner che è molto approssimativamente equivalente a:

def sumEach(myLists):
    return [sum(_) for _ in myLists]
  • Basta usare un for-loop : puoi anche ovviamente usare un for-loop. Sebbene non siano così eleganti dal punto di vista della programmazione funzionale, a volte variabili non locali rendono il codice più chiaro nei linguaggi di programmazione imperativa come Python, perché le persone sono molto abituate a leggere il codice in quel modo. I for-loop sono anche, generalmente, i più efficienti quando stai semplicemente facendo qualsiasi operazione complessa che non sta costruendo un elenco come la comprensione delle liste e la mappa per cui sono ottimizzati (ad esempio sommando o facendo un albero, ecc.) - almeno efficiente in termini di memoria (non necessariamente in termini di tempo, dove mi aspetterei nella peggiore delle ipotesi un fattore costante, salvo qualche raro singhiozzo patologico di raccolta dei rifiuti).

"Pythonism"

Non mi piace la parola "pitone" perché non trovo che pitone sia sempre elegante ai miei occhi. Tuttavia, mape filterfunzioni simili (come il itertoolsmodulo molto utile ) sono probabilmente considerate non ritoniche in termini di stile.

Pigrizia

In termini di efficienza, come la maggior parte dei costrutti di programmazione funzionale, MAP PU BE ESSERE LUSSUOSO , e in effetti è pigro in pitone. Ciò significa che puoi farlo (in python3 ) e il tuo computer non esaurirà la memoria e perderà tutti i tuoi dati non salvati:

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

Prova a farlo con una comprensione dell'elenco:

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

Nota che anche le comprensioni degli elenchi sono intrinsecamente pigre, ma Python ha scelto di implementarle come non pigre . Tuttavia, python supporta la comprensione dell'elenco pigro sotto forma di espressioni generatrici, come segue:

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

Fondamentalmente puoi pensare alla [...]sintassi come passare un'espressione di generatore al costruttore della lista, come list(x for x in range(5)).

Breve esempio inventato

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

Le comprensioni dell'elenco sono non pigre, quindi potrebbe richiedere più memoria (a meno che non si utilizzino le comprensioni del generatore). Le parentesi quadre [...]spesso rendono le cose ovvie, specialmente quando si trovano in un pasticcio di parentesi. D'altra parte, a volte finisci per essere prolisso come scrivere [x for x in.... Finché si mantengono brevi le variabili dell'iteratore, le comprensioni degli elenchi sono generalmente più chiare se non si rientra il codice. Ma puoi sempre indentare il tuo codice.

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

o rompere le cose:

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

Confronto di efficienza per python3

map è ora pigro:

% python3 -mtimeit -s 'xs=range(1000)' 'f=lambda x:x' 'z=map(f,xs)'
1000000 loops, best of 3: 0.336 usec per loop            ^^^^^^^^^

Pertanto, se non mapuserete tutti i vostri dati, o non saprete in anticipo di quanti dati avete bisogno, in python3 (ed espressioni del generatore in python2 o python3) eviterete di calcolarne i valori fino all'ultimo momento necessario. Di solito questo supererà di gran lunga qualsiasi sovraccarico dall'uso map. Il rovescio della medaglia è che questo è molto limitato in Python rispetto alla maggior parte dei linguaggi funzionali: ottieni questo vantaggio solo se accedi ai tuoi dati da sinistra a destra "in ordine", perché le espressioni del generatore python possono essere valutate solo nell'ordine x[0], x[1], x[2], ....

Tuttavia, diciamo che abbiamo una funzione pre-creata fche vorremmo mape ignoriamo la pigrizia mapforzando immediatamente la valutazione list(...). Otteniamo risultati molto interessanti:

% 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>)

I risultati sono nella forma AAA / BBB / CCC dove A è stato eseguito con una workstation Intel del 2010 circa con Python 3.?.?, E B e C sono stati eseguiti con una workstation AMD del 2013 circa con Python 3.2.1, con hardware estremamente diverso. Il risultato sembra essere che la comprensione di mappe ed elenchi sia paragonabile nelle prestazioni, che è fortemente influenzata da altri fattori casuali. L'unica cosa che possiamo dire sembra essere che, stranamente, mentre ci aspettiamo che la comprensione dell'elenco [...]funzioni meglio delle espressioni del generatore (...), mapè ANCHE più efficiente delle espressioni del generatore (sempre supponendo che tutti i valori vengano valutati / utilizzati).

È importante rendersi conto che questi test assumono una funzione molto semplice (la funzione identità); tuttavia questo va bene perché se la funzione fosse complicata, il sovraccarico delle prestazioni sarebbe trascurabile rispetto ad altri fattori nel programma. (Potrebbe essere comunque interessante testare con altre cose semplici come f=lambda x:x+x)

Se sei abile a leggere l'assembly di Python, puoi usare il dismodulo per vedere se è proprio quello che succede dietro le quinte:

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

Sembra che sia meglio usare la [...]sintassi di list(...). Purtroppo la mapclasse è un po 'opaca da disassemblare, ma possiamo farcela con il nostro test di velocità.


5
"l'utilissimo modulo itertools [è] probabilmente considerato non pitonico in termini di stile". Hmm. Non mi piace nemmeno il termine "Pythonic", quindi in un certo senso non mi interessa cosa significhi, ma non penso sia giusto per chi lo usa, dire che secondo i builtin di "Pythonicness" mape filterinsieme alla libreria standard itertoolshanno uno stile intrinsecamente cattivo. A meno che GvR in realtà non dica che sono stati un terribile errore o esclusivamente per le prestazioni, l'unica conclusione naturale se questo è ciò che dice "Pythonicness" è di dimenticarsene come stupido ;-)
Steve Jessop,

4
@SteveJessop: In realtà, Guido pensava che abbandonare map/ filterfosse una grande idea per Python 3 , e solo una ribellione di altri Pythonistas li teneva nello spazio dei nomi incorporato (mentre reduceveniva spostato in functools). Personalmente non sono d' accordo ( mape sto filterbene con funzioni predefinite, in particolare integrate, ma non le uso mai se lambdafosse necessario), ma GvR non le ha praticamente definite Pythonic per anni.
ShadowRanger,

@ShadowRanger: vero, ma GvR aveva mai intenzione di rimuoverlo itertools? La parte che cito da questa risposta è l'affermazione principale che mi confonde. Non so se nel suo mondo ideale, mape filtersi sposterebbe itertools(o functools) o andrebbe del tutto, ma qualunque sia il caso, una volta che si dice che non itertoolsè Pitonico nella sua interezza, allora non so davvero cosa sia "Pythonic" dovrebbe significare, ma non credo che possa essere qualcosa di simile a "ciò che GvR raccomanda alle persone di usare".
Steve Jessop,

2
@SteveJessop: stavo solo affrontando map/ filter, no itertools. La programmazione funzionale è perfettamente Pythonic ( itertools, functoolse operatorsono stati tutti progettati specificamente con la programmazione funzionale in mente, e io uso idiomi funzionali in Python tutto il tempo), e itertoolsfornisce funzionalità che sarebbe un dolore per attuare da soli, E 'specificamente mape filterdi essere ridondante con le espressioni del generatore ciò li fece odiare da Guido. itertoolsè sempre andato bene.
ShadowRanger,

1
Potrei preferire questa risposta se ci fosse un modo. Ben spiegato.
Nelson Gon,

95

Python 2: dovresti usare mape filterinvece della comprensione dell'elenco.

Un motivo oggettivo per cui dovresti preferirli anche se non sono "Pythonic" è questo:
richiedono funzioni / lambdas come argomenti, che introducono un nuovo ambito .

Sono stato morso da questo più di una volta:

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

ma se invece avessi detto:

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

allora tutto sarebbe andato bene.

Si potrebbe dire che ero sciocco per aver usato lo stesso nome di variabile nello stesso ambito.

Non lo ero. Inizialmente il codice andava bene, i due xnon erano nello stesso ambito.
È stato solo dopo che mi sono trasferito il blocco interno in un'altra sezione del codice è emerso il problema (leggi: problema durante la manutenzione, non in fase di sviluppo) e non me lo aspettavo.

Sì, se non commetti questo errore, la comprensione delle liste è più elegante.
Ma per esperienza personale (e dal vedere gli altri commettere lo stesso errore) l'ho visto accadere abbastanza volte che penso che non valga la pena soffrire quando questi bug si insinuano nel tuo codice.

Conclusione:

Usa mape filter. Impediscono bug impercettibili e difficili da diagnosticare.

Nota a margine:

Non dimenticare di considerare l'utilizzo imape ifilter(in itertools) se sono appropriati per la tua situazione!


7
Grazie per averlo segnalato. Non mi era venuto in mente esplicitamente che la comprensione dell'elenco fosse nello stesso ambito e potesse essere un problema. Detto questo, penso che alcune delle altre risposte chiariscano che la comprensione dell'elenco dovrebbe essere l'approccio predefinito per la maggior parte del tempo, ma questo è qualcosa da ricordare. Questo è anche un buon promemoria generale per mantenere le funzioni (e quindi la portata) piccole e avere test unitari approfonditi e utilizzare dichiarazioni di asserzione.
TimothyAwiseman,

13
@wim: si trattava solo di Python 2, sebbene si applichi a Python 3 se si desidera rimanere compatibili con le versioni precedenti. Lo sapevo e usavo Python da un po 'di tempo (sì, più di pochi mesi), eppure mi è successo. Ho visto altri che sono più intelligenti di me cadere nella stessa trappola. Se sei così brillante e / o esperto che questo non è un problema per te, allora sono felice per te, non penso che la maggior parte delle persone sia come te. Se lo fossero, non ci sarebbe un tale bisogno di risolverlo in Python 3.
user541686

12
Mi dispiace ma l'hai scritto alla fine del 2012, ben dopo che python 3 è sulla scena e la risposta dice che stai raccomandando uno stile altrimenti impopolare di codifica Python solo perché sei stato morso da un bug durante il taglio e codice di incollaggio. Non ho mai affermato di essere brillante o esperto, semplicemente non sono d'accordo sul fatto che l'affermazione audace sia giustificata dalle tue ragioni.
mercoledì

8
@wim: eh? Python 2 è ancora usato in molti posti, il fatto che Python 3 esista non lo cambia. E quando dici "non è esattamente un bug sottile per chiunque abbia usato Python per più di qualche mese" quella frase significa letteralmente "questo riguarda solo sviluppatori inesperti" (chiaramente non tu). E per la cronaca, chiaramente non hai letto la risposta perché ho detto in grassetto che stavo spostando , non copiando, il codice. I bug di copia-incolla sono abbastanza uniformi in tutte le lingue. Questo tipo di bug è più unico di Python a causa del suo ambito; è più sottile e più facile da dimenticare e perdere.
user541686

3
Non è ancora un motivo logico per passare a mape / o filter. Semmai, la traduzione più diretta e logica per evitare il tuo problema non è map(lambda x: x ** 2, numbers)ma piuttosto un'espressione del generatore list(x ** 2 for x in numbers)che non perde, come ha già sottolineato JeromeJ. Guarda Mehrdad, non prendere un voto così personale, non sono assolutamente d'accordo con il tuo ragionamento qui.
mercoledì

46

In realtà, la mapcomprensione dell'elenco si comporta in modo abbastanza diverso nel linguaggio Python 3. Dai un'occhiata al seguente programma Python 3:

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

Potresti aspettarti che stampi la riga "[1, 4, 9]" due volte, ma invece stampa "[1, 4, 9]" seguito da "[]". La prima volta che lo guardi squaressembra comportarsi come una sequenza di tre elementi, ma la seconda volta come uno vuoto.

Nel linguaggio Python 2 maprestituisce un vecchio elenco semplice, proprio come fanno le comprensioni degli elenchi in entrambe le lingue. Il punto cruciale è che il valore restituito mapin Python 3 (e imapin Python 2) non è un elenco - è un iteratore!

Gli elementi vengono consumati quando si scorre su un iteratore, diversamente da quando si scorre su un elenco. Questo è il motivo per cui squaresappare vuoto nell'ultima print(list(squares))riga.

Riassumere:

  • Quando hai a che fare con gli iteratori devi ricordare che sono statali e che mutano mentre li attraversi.
  • Gli elenchi sono più prevedibili poiché cambiano solo quando li muthi esplicitamente; sono contenitori .
  • E un vantaggio: numeri, stringhe e tuple sono ancora più prevedibili poiché non possono cambiare affatto; sono valori .

questo è probabilmente l'argomento migliore per la comprensione dell'elenco. la mappa dei pitoni non è la mappa funzionale ma il figliastro dalla testa rossa paralizzato di un'implementazione funzionale. Molto triste, perché non mi piacciono molto le comprensioni.
semiomante

@semiomant Direi che la mappa pigra (come in python3) è più "funzionale" della mappa desiderosa (come in python2). Ad esempio, la mappa in Haskell è pigra (beh, tutto in Haskell è pigro ...). Comunque, la mappa pigra è migliore per concatenare le mappe: se hai una mappa applicata alla mappa applicata alla mappa, hai un elenco per ciascuna delle chiamate intermedie della mappa in python2, mentre in python3 hai solo un elenco risultante, quindi è più efficiente della memoria .
MnZrK,

Immagino che ciò che voglio sia mapprodurre una struttura di dati, non un iteratore. Ma forse gli iteratori pigri sono più facili delle strutture di dati pigri. Cibo per la mente. Grazie @MnZrK
semiomant

Vuoi dire che la mappa restituisce un iterabile, non un iteratore.
user541686,

16

Trovo che le comprensioni degli elenchi siano generalmente più espressive di ciò che sto cercando di fare rispetto a map- entrambi lo fanno, ma il primo salva il carico mentale di cercare di capire quale potrebbe essere lambdaun'espressione complessa .

C'è anche un'intervista là fuori da qualche parte (non riesco a trovarla in maniera sprovvista) in cui Guido elenca lambdale funzioni funzionali come la cosa di cui più si rammarica di accettare in Python, quindi potresti argomentare che sono non-Pythonic in virtù di quella.


9
Sì, sospiro, ma l'intenzione originale di Guido di rimuovere del tutto lambda in Python 3 ha avuto una raffica di pressioni contro di esso, quindi è tornato su di esso nonostante il mio supporto robusto - ah beh, suppongo che lambda sia troppo utile in molti casi SEMPLICI , l'unico il problema è quando supera i limiti di SEMPLICE o viene assegnato a un nome (in questo caso è un duplicato sciocco di def! -).
Alex Martelli,

1
L'intervista a cui stai pensando è questa: amk.ca/python/writing/gvr-interview , in cui Guido dice "A volte sono stato troppo veloce nell'accettare i contributi e in seguito ho capito che si trattava di un errore. Un esempio potrebbe essere alcune delle funzionalità di programmazione funzionale, come le funzioni lambda. lambda è una parola chiave che consente di creare una piccola funzione anonima; funzioni integrate come mappa, filtro e riduzione eseguono una funzione su un tipo di sequenza, come un elenco. "
J. Taylor,

3
@Alex, non ho i tuoi anni di esperienza, ma ho visto una comprensione dell'elenco molto più complicata rispetto a lambdas. Naturalmente, abusare delle funzionalità del linguaggio è sempre una tentazione difficile da resistere. È interessante notare che le comprensioni degli elenchi (empiricamente) sembrano più inclini agli abusi rispetto agli lambda, anche se non sono sicuro del motivo. Sottolineerò anche che "hobbled" non è sempre una brutta cosa. Ridurre la portata di "cose ​​che questa linea potrebbe fare" a volte può rendere più semplice per il lettore. Ad esempio, la constparola chiave in C ++ è un grande trionfo su queste linee.
Stuart Berg,

> guido. Il che è un'altra prova che Guido è fuori di testa. Ovviamente lambdasono stati resi così scadenti (nessuna dichiarazione ..) che sono difficili da usare e comunque limitati.
javadba,

16

Ecco un possibile caso:

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

contro:

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

Immagino che zip () sia uno svantaggio sfortunato e inutile che devi indulgere se insisti nell'usare la comprensione dell'elenco anziché la mappa. Sarebbe bello se qualcuno chiarisse questo in modo affermativo o negativo.


"[op1 * op2 da op1, op2 in zip (list1, list2)]" | s / form / for / E un elenco equivalente senza zip: (meno leggibile) [list1 [i] * list2 [i] per i nel range (len (list1))]
debole

2
Dovrebbe essere "per" non "da" nella tua seconda citazione di codice, @andz, e anche nel commento di @ weakish. Pensavo di aver scoperto un nuovo approccio sintattico per elencare le comprensioni ... Dannazione.
Physicsmichael,

4
per aggiungere un commento molto tardi, puoi renderti zippigro usandoitertools.izip
tacaswell il

5
Penso di preferire ancora map(operator.mul, list1, list2). È su queste semplicissime espressioni sul lato sinistro che la comprensione diventa goffa.
Yann Vernier

1
Non mi ero reso conto che la mappa potesse prendere diversi iterabili come input per la sua funzione e quindi evitare una zip.
Bla

16

Se hai intenzione di scrivere un codice asincrono, parallelo o distribuito, probabilmente preferirai mapuna comprensione dell'elenco - poiché la maggior parte dei pacchetti asincroni, paralleli o distribuiti fornisce una mapfunzione per sovraccaricare Python map. Quindi passando la mapfunzione appropriata al resto del codice, potrebbe non essere necessario modificare il codice seriale originale per farlo funzionare in parallelo (ecc.).



1
Il modulo multiprocessing di Python lo fa: docs.python.org/2/library/multiprocessing.html
Robert L.

9

Quindi, dato che Python 3 map()è un iteratore, devi tenere a mente ciò di cui hai bisogno: un iteratore o un listoggetto.

Come già accennato @AlexMartelli , map()è più veloce della comprensione dell'elenco solo se non si utilizza la lambdafunzione.

Ti presenterò alcuni paragoni di tempo.

Python 3.5.2 e CPython
ho usato il taccuino di Giove e in particolare il %timeitcomando magico incorporato
Misure : s == 1000 ms == 1000 * 1000 µs = 1000 * 1000 * 1000 ns

Impostare:

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

Funzione integrata:

%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 funzione:

%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

Esiste anche un'espressione del generatore, vedi PEP-0289 . Quindi ho pensato che sarebbe stato utile aggiungerlo al confronto

%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

Hai bisogno di un listoggetto:

Usa la comprensione dell'elenco se è una funzione personalizzata, usa list(map())se c'è una funzione integrata

Non hai bisogno di listoggetti, hai solo bisogno di uno iterabile:

Usa sempre map()!


1

Ho eseguito un test rapido confrontando tre metodi per invocare il metodo di un oggetto. La differenza di tempo, in questo caso, è trascurabile ed è una questione della funzione in questione (vedere la risposta di @Alex Martelli ). Qui, ho esaminato i seguenti metodi:

# 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]

Ho esaminato gli elenchi (memorizzati nella variabile vals) di numeri interi (Python int) e in virgola mobile (Python float) per aumentare le dimensioni dell'elenco. È considerata la seguente classe fittizia DummyNum:

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

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

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

In particolare, il addmetodo. L' __slots__attributo è una semplice ottimizzazione in Python per definire la memoria totale richiesta dalla classe (attributi), riducendo la dimensione della memoria. Ecco i grafici risultanti.

Prestazioni di mappatura dei metodi degli oggetti Python

Come affermato in precedenza, la tecnica utilizzata fa una differenza minima e dovresti codificare in un modo che sia più leggibile per te o nella circostanza particolare. In questo caso, la comprensione dell'elenco ( map_comprehensiontecnica) è più rapida per entrambi i tipi di aggiunte in un oggetto, in particolare con elenchi più brevi.

Visita questo pastebin per la fonte utilizzata per generare la trama e i dati.


1
Come già spiegato in altre risposte, mapè più veloce solo se la funzione viene chiamata nello stesso identico modo (cioè [*map(f, vals)]vs. [f(x) for x in vals]). Quindi list(map(methodcaller("add"), vals))è più veloce di [methodcaller("add")(x) for x in vals]. mappotrebbe non essere più veloce quando la controparte ciclica utilizza un diverso metodo di chiamata che può evitare un certo sovraccarico (ad esempio, x.add()evita il methodcallersovraccarico dell'espressione o lambda). Per questo specifico test case, [*map(DummyNum.add, vals)]sarebbe più veloce (perché DummyNum.add(x)e x.add()sostanzialmente ha le stesse prestazioni).
GZ0

1
A proposito, le list()chiamate esplicite sono leggermente più lente della comprensione dell'elenco. Per un confronto equo devi scrivere [*map(...)].
GZ0

@ GZ0 grazie per l'ottimo feedback! Tutto ha un senso, e non ero a conoscenza del fatto che le list()chiamate aumentassero le spese generali. Avrei dovuto passare più tempo a leggere le risposte. Eseguirò nuovamente questi test per un confronto equo, per quanto trascurabili possano essere le differenze.
Craymichael,

0

Ritengo che il modo più Pythonic sia usare una comprensione della lista invece di mape filter. Il motivo è che la comprensione dell'elenco è più chiara di mape 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

Come vedi, una comprensione non richiede lambdaespressioni extra come mapnecessità. Inoltre, una comprensione consente anche di filtrare facilmente, mentre maprichiede filterdi consentire il filtraggio.


0

Ho provato il codice di @ alex-martelli ma ho riscontrato alcune discrepanze

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

map impiega lo stesso tempo anche per intervalli molto ampi, mentre l'utilizzo della comprensione dell'elenco richiede molto tempo, come è evidente dal mio codice. Quindi, a parte il fatto di essere considerato "non pitonico", non ho affrontato alcun problema di prestazioni relativo all'utilizzo della mappa.


3
Questa è una domanda molto antica e la risposta a cui ti riferisci è stata molto probabilmente scritta in riferimento a Python 2, dove maprestituisce un elenco. In Python 3 mapviene valutato pigramente, quindi la semplice chiamata mapnon calcola nessuno dei nuovi elementi dell'elenco, quindi perché si ottengono tempi così brevi.
kaya3,

Penso che tu stia usando Python 3.x Quando ho posto questa domanda, Python 3 era stato rilasciato solo di recente e Python 2.x era molto lo standard.
TimothyAWiseman il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.