Domanda

C'è un motivo per preferire l'utilizzo map() rispetto alla comprensione delle liste o viceversa?Uno dei due è generalmente più efficiente o considerato generalmente più pitonico dell'altro?

È stato utile?

Soluzione

map può essere microscopicamente più veloce in alcuni casi (quando non stai facendo una lambda allo scopo, ma utilizzando la stessa funzione in mappa e un listcomp). Di lista possono essere più veloce in altri casi e la maggior parte (non tutti) Pythonisti li considerano più diretto e più chiaro.

Un esempio del piccolo vantaggio della velocità di carta 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 confronto delle prestazioni viene completamente invertita quando ha bisogno di una mappa 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

Altri suggerimenti

Casi

  • Caso comune:Quasi sempre, vorrai utilizzare una comprensione di elenco in pitone perché sarà più ovvio cosa stai facendo ai programmatori alle prime armi che leggono il tuo codice.(Questo non si applica ad altri linguaggi, dove possono applicarsi altri idiomi.) Sarà ancora più ovvio cosa stai facendo per i programmatori Python, poiché le comprensioni delle liste sono lo standard di fatto in Python per l'iterazione;sono previsto.
  • Caso meno comune:Tuttavia se tu hanno già una funzione definita, è spesso ragionevole da usare map, sebbene sia considerato "non pittonico".Per esempio, map(sum, myLists) è più elegante/conciso di [sum(x) for x in myLists].Ottieni l'eleganza di non dover creare una variabile fittizia (ad es. sum(x) for x... O sum(_) for _... O sum(readableName) for readableName...) che devi digitare due volte, solo per ripetere.Lo stesso discorso vale per filter E reduce e qualsiasi cosa da itertools modulo:se hai già una funzione a portata di mano, potresti andare avanti e fare un po' di programmazione funzionale.Ciò guadagna leggibilità in alcune situazioni e la perde in altre (ad es.programmatori alle prime armi, argomenti multipli)...ma la leggibilità del tuo codice dipende comunque molto dai tuoi commenti.
  • Quasi mai:Potresti voler usare il map funzionare come una pura funzione astratta mentre si esegue la programmazione funzionale, in cui si esegue la mappatura map, o curry map, o altrimenti trarre vantaggio dal parlarne map come una funzione.In Haskell, ad esempio, un'interfaccia funtore chiamata fmap generalizza la mappatura su qualsiasi struttura dati.Questo è molto raro in Python perché la grammatica di Python ti obbliga a usare lo stile generatore per parlare di iterazione;non puoi generalizzarlo facilmente.(Questo a volte è positivo e talvolta negativo.) Probabilmente puoi trovare rari esempi di Python dove map(f, *lists) è una cosa ragionevole da fare.L'esempio più vicino che riesco a trovare sarebbe sumEach = partial(map,sum), che è una riga che equivale più o meno a:

def sumEach(myLists):
    return [sum(_) for _ in myLists]
  • Basta usare a for-ciclo continuo:Ovviamente puoi anche usare semplicemente un ciclo for.Sebbene non siano così eleganti dal punto di vista della programmazione funzionale, a volte le variabili non locali rendono il codice più chiaro nei linguaggi di programmazione imperativi come Python, perché le persone sono molto abituate a leggere il codice in questo modo.I cicli for sono anche, generalmente, i più efficienti quando si esegue semplicemente un'operazione complessa che non sta costruendo un elenco come list-comprehension e map sono ottimizzati per (ad es.sommando, o creando un albero, ecc.) - almeno efficiente in termini di memoria (non necessariamente in termini di tempo, dove mi aspetterei nel peggiore dei casi un fattore costante, salvo qualche raro singhiozzo patologico nella raccolta dei rifiuti).

"Pitonismo"

Non mi piace la parola "pythonic" perché non trovo che Python sia sempre elegante ai miei occhi.Tuttavia, map E filter e funzioni simili (come l'utilissimo itertools module) sono probabilmente considerati non pitonici in termini di stile.

Pigrizia

In termini di efficienza, come la maggior parte dei costrutti di programmazione funzionale, LA MAPPA PUÒ ESSERE PIGRO, e in effetti è pigro in Python.Ciò significa che puoi farlo (in python3) e il tuo computer non esaurirà la memoria e perderà tutti i 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 #

Tieni presente che anche la comprensione delle liste è intrinsecamente pigra, ma Python ha scelto di implementarli come non pigri.Tuttavia, Python supporta la comprensione delle liste pigre sotto forma di espressioni del generatore, come segue:

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

In pratica puoi pensare a [...] sintassi come passaggio di un'espressione del 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 degli elenchi non sono pigre, quindi potrebbero richiedere più memoria (a meno che non si utilizzino le comprensioni del generatore).Le parentesi quadre [...] spesso rendono le cose ovvie, soprattutto quando sono in mezzo a parentesi.D'altra parte, a volte finisci per essere prolisso come scrivere [x for x in....Finché mantieni le variabili dell'iteratore brevi, la comprensione dell'elenco è generalmente più chiara se non indenti il ​​codice.Ma potresti sempre indentare il tuo codice.

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

o spezzare 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 utilizzerai tutti i tuoi dati o non sai in anticipo di quanti dati avrai bisogno, map in python3 (e le espressioni del generatore in python2 o python3) eviteranno di calcolare i loro valori fino all'ultimo momento necessario.Di solito questo supera qualsiasi sovraccarico derivante dall'utilizzo map.Lo svantaggio è 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 predefinita f ci piacerebbe map, e ignoriamo la pigrizia di map forzando immediatamente la valutazione con list(...).Otteniamo alcuni 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 nel formato AAA/BBB/CCC dove A è stato eseguito su 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 e liste è comparabile in termini di prestazioni, che sono fortemente influenzate da altri fattori casuali.L'unica cosa che possiamo dire sembra essere quella, stranamente, mentre ci aspettiamo la comprensione delle liste [...] per funzionare meglio delle espressioni del generatore (...), map è ANCHE più efficiente del generatore di espressioni (supponendo nuovamente 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 esperto nella lettura dell'assembly Python, puoi utilizzare il file dis modulo per vedere se è effettivamente quello che sta succedendo 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 usarlo [...] sintassi di list(...).Purtroppo il map La classe è un po' opaca da smontare, ma possiamo farcela con il nostro test di velocità.

Python 2: Si dovrebbe usare map e filter invece di list comprehension

.

Un obiettivo motivo per cui si dovrebbe preferire loro anche se non sono "Pythonic" è questo:
Essi richiedono funzioni / lambda come argomenti, che introdurre un nuovo ambito .

ho ottenuto morso da 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 stato bene.

Si potrebbe dire che ero essere stupido per usare lo stesso nome di variabile nello stesso ambito.

Non ero. Il codice è stato bene in origine - i due xs non erano nello stesso ambito
. E 'stato solo dopo che I spostato il blocco interno ad una diversa sezione del codice che il problema è venuto in su. (Leggi: problema durante la manutenzione, non lo sviluppo), e non mi aspettavo che

Sì, se non fate questo errore , allora list comprehension sono più elegante.
Ma per esperienza personale (e di vedere gli altri fanno lo stesso errore) che ho visto accadere abbastanza volte che penso che non vale la pena il dolore si deve passare attraverso quando questi bug si insinuano nel codice.

Conclusione:

Usa map e filter. Impediscono sottili bug difficili da diagnosticare portata correlati.

Nota a margine:

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

In realtà, map e list comprehension si comportano in modo diverso nel linguaggio Python 3. Date 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))

Si potrebbe aspettare per stampare la linea "[1, 4, 9]" due volte, ma invece esso stampa "[1, 4, 9]" seguito da "[]". La prima volta che si guarda squares sembra comportarsi come una sequenza di tre elementi, ma la seconda volta come una vuota.

Nel linguaggio map Python 2 restituisce una pianura lista vecchia, proprio come fanno list comprehension in entrambe le lingue. Il punto cruciale è che il valore di ritorno di map in Python 3 (e imap in Python 2) non è un elenco - si tratta di un iteratore

Gli elementi vengono consumati quando eseguire iterazioni su un iteratore a differenza di quando eseguire iterazioni su una lista. Questo è il motivo per cui squares sembra vuoto nell'ultima riga print(list(squares)).

Per riassumere:

  • Quando si tratta di iteratori si deve ricordare che essi sono stateful e che mutano come li attraversano.
  • Le liste sono più prevedibili dal momento che solo cambiano quando li processo di mutazione in modo esplicito; sono contenitori .
  • e un bonus: i numeri, stringhe e tuple sono ancora più prevedibili dal momento che non possono cambiare a tutti; sono Valori .

Trovo list comprehension sono generalmente più espressivo di quello che sto cercando di fare che map - entrambi avere fatto, ma il primo fa risparmiare il carico mentale di cercare di capire quello che potrebbe essere un'espressione lambda complesso

C'è anche un'intervista là fuori da qualche parte (io non riesco a trovarlo due piedi), dove Guido elenca lambdas e le funzioni funzionali come la cosa che più rimpianti di accettare in Python, così si potrebbe fare l'argomento che sono non- Pythonic in virtù di questo.

Qui è un caso possibile:

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

vs

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

Sono indovinando la zip () è una sfortunata e inutili spese generali è necessario indulgere in se ti ostini a usare list comprehensions della mappa. Sarebbe bello se qualcuno chiarisce questo sia affermativamente o negativamente.

Se hai intenzione di scrivere alcun asincrono, in parallelo, o il codice distribuito, probabilmente si preferisce map su una lista di comprensione - come la maggior parte asincrono, in parallelo, o pacchetti distribuiti forniscono una funzione map sovraccaricare map di pitone. Poi passando la funzione map appropriato al resto del codice, non potrebbe essere necessario modificare il codice di serie originale per fare eseguire in parallelo (etc).

Quindi, dal momento Python 3, map() è un iteratore, è bisogno di tenere a mente che cosa hai bisogno:. un oggetto iteratore o list

Come già @AlexMartelli accennato , map() è più veloce rispetto di lista solo se non si utilizza la funzione lambda.

vi presenterò alcuni confronti di tempo.

Python 3.5.2 e CPython
ho usato Giove notebook e soprattutto %timeit comando integrato magia
Misure : s == 1000 ms == 1000 * 1000 ms = 1000 * 1000 * 1000 ns

Configurazione:

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

Funzione incorporata:

%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

Funzione lambda:

%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

C'è anche una cosa come generatore di espressione, vedi PEP- 0289 . Così ho pensato che sarebbe stato utile aggiungere 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

È necessario oggetto list:

L'uso di lista se si tratta di funzione personalizzata, l'uso list(map()) se c'è funzione built-in

Non hai bisogno oggetto list, basta iterabili uno:

Usa sempre map()!

ritengo che il modo più Pythonic è quello di utilizzare un elenco di comprensione, invece di map e filter. La ragione è che list comprehension sono più chiare di quanto map e 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 si vede una, la comprensione non richiede espressioni lambda extra come esigenze map. Inoltre, una comprensione permette anche filtraggio facilmente, mentre map richiede filter per consentire il filtraggio.

Ho eseguito un test rapido confronto tra tre metodi per invocare il metodo di un oggetto. La differenza di tempo, in questo caso, è trascurabile ed è una questione di funzione in questione (vedi di @alex Martelli risposta href="https://stackoverflow.com/a/1247490/6557588"> ) . Qui, ho guardato 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 guardato liste (memorizzati nella vals variabile) di entrambi i numeri interi (Python int) e numeri in virgola mobile (Python float) per aumentare le dimensioni della lista. Il seguente DummyNum classe fittizia è considerato:

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

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

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

In particolare, il metodo add. L'attributo __slots__ è un semplice ottimizzazione in Python per definire la memoria totale necessaria per la classe (attributi), riducendo la dimensione della memoria. Qui ci sono le trame che ne derivano.

performance di metodi dell'oggetto mappatura Python

Come affermato in precedenza, la tecnica utilizzata fa la differenza minima e si dovrebbe codice in un modo che è più leggibile a voi, o nella particolare circostanza. In questo caso, la lista di comprensione (tecnica map_comprehension) è il più veloce per entrambi i tipi di aggiunte in un oggetto, in particolare con le liste più brevi.

questo pastebin per la sorgente utilizzata per generare la trama e dei dati.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top