2009-12-29 10 views
33

Esta es relativa a lo siguiente: (En el código Python)¿Cuándo debería usar un mapa en lugar de un bucle for?

for i in object: 
    doSomething(i) 

frente

map(doSomething, object) 

Ambos son fáciles de entender, y corto, pero ¿hay alguna diferencia de velocidad? Ahora, si doSomething tenía un valor de retorno, necesitábamos verificar que sería devuelto como una lista del mapa, y en el ciclo for podríamos crear nuestra propia lista o verificarla de a una por vez.

for i in object: 
    returnValue = doSomething(i) 
    doSomethingWithReturnValue(returnValue) 

frente

returnValue = map(doSomething, object) 
map(doSomethingWithReturnValue, returnValue) 

Ahora, me siento los dos divergen un poco. Las dos funciones doSomethingWithReturnValue pueden ser diferentes en función de si las verificamos sobre la marcha a medida que avanzamos en el ciclo o si al verificarlas una vez al final se obtienen resultados diferentes. También parece que el bucle for siempre funcionaría, quizás más lentamente, donde el mapa solo funcionaría bajo ciertos escenarios. Por supuesto, podríamos hacer contorsiones para que cualquiera de los dos trabaje, pero el objetivo es evitar este tipo de trabajo.

Lo que estoy buscando es un escenario donde la función de mapeo realmente brilla en comparación con un ciclo bien hecho en cuanto a rendimiento, legibilidad, mantenibilidad o velocidad de implementación. Si la respuesta es que realmente no hay una gran diferencia, me gustaría saber cuándo en la práctica las personas usan una u otra o si es realmente completamente arbitraria y se establece mediante normas de codificación según su institución.

Gracias!

+2

Puede usar la lista/diccionario/conjunto de comprensión o un generador en lugar de un mapa, depende de lo que haga algo(). –

+2

no intente ajustar el rendimiento preliminar. siempre tomaría la mejor opción legible. el tiempo de ejecución posterior mostrará si el rendimiento destacado es un problema y usted tiene una mejor velocidad o uso de recursos. –

Respuesta

22

map es útil cuando desea aplicar la función a cada elemento de un iterable y devolver una lista de los resultados. Esto es más simple y más conciso que usar un bucle for y construir una lista.

for es a menudo más legible para otras situaciones, y en lisp hubo muchas construcciones de iteración que se escribieron básicamente utilizando macros y mapas. Por lo tanto, en los casos en que map no se ajusta, utilice un bucle for.

En teoría, si tuviéramos un compilador/intérprete lo suficientemente inteligente como para hacer uso de varias CPU/procesadores, entonces map podría implementarse más rápido ya que las diferentes operaciones en cada elemento podrían realizarse en paralelo. Sin embargo, no creo que este sea el caso en este momento.

+0

PLINQ (C#) puede hacer eso sin embargo. –

+12

¿Por qué el tiempo pasado? Lisp está vivo y coleando. – Svante

+3

En realidad 'map' late' for' en el rendimiento incluso en un solo hilo porque el ciclo está escrito en C. Vea mi publicación para la prueba de velocidad. – iamamac

7

solo usa la lista de comprensiones: son más pitónicas. También tienen una sintaxis similar a las expresiones del generador que facilita el cambio de una a la otra. No necesita cambiar nada al convertir su código a py3k: map devuelve un iterable en py3k y tendrá que ajustar su código.

si no te importan los valores devueltos simplemente no nombras la nueva lista, necesitas usar los valores de retorno una vez en tu código puedes cambiar a las expresiones del generador y una sola lista de comprensión al final.

0

EDIT: No me di cuenta de que map es igual a itertools.imap después de Python 3.0. Entonces, la conclusión aquí puede no ser correcta. Volveré a ejecutar la prueba en Python 2.6 mañana y publicaré el resultado.

Si doSomething es muy "pequeño", map puede ser mucho más rápido que for bucle o una lista-comprensión:

# Python 3.1.1 (r311:74483, Aug 17 2009, 17:02:12) [MSC v.1500 32 bit (Intel)] on win32 

from timeit import timeit 

do = lambda i: i+1 

def _for(): 
    for i in range(1000): 
    do(i) 

def _map(): 
    map(do, range(1000)) 

def _list(): 
    [do(i) for i in range(1000)] 

timeit(_for, number=10000) # 2.5515936921388516 
timeit(_map, number=10000) # 0.010167432629884843 
timeit(_list, number=10000) # 3.090125159839033 

Esto se debe a map está escrito en C, mientras que for bucle y lista-comprensión ejecutada en la máquina virtual python.

+1

No sé de dónde sacas tus números, pero en mi caso (Python 2.6) el bucle for es más rápido, en aproximadamente un 5%. Su código ni siquiera es correcto, ya que _list realiza menos iteraciones. Las enormes diferencias que tienes indican que algo está muy mal con tu configuración. – interjay

+0

esto es simplemente ridículo. tus códigos no son equivalentes en absoluto. lee mi respuesta. incluso sin mapa objeto vs. lista dicotomía tu código es diferente, es una pena que no lo veas – SilentGhost

+0

Lo siento, hay un error de tipeo cuando copié el código y lo formateé. Ejecuto Python 3.1.1 en mi PC Core Duo 4300, 'map' supera significativamente a los otros dos. – iamamac

10

¿Está familiarizado con el módulo timeit? A continuación se muestran algunos tiempos. -s realiza una configuración de una sola vez, y luego se realiza un bucle en el comando y se registra el mejor tiempo.

1> python -m timeit -s "L=[]; M=range(1000)" "for m in M: L.append(m*2)" 
1000 loops, best of 3: 432 usec per loop 

2> python -m timeit -s "M=range(1000);f=lambda x: x*2" "L=map(f,M)" 
1000 loops, best of 3: 449 usec per loop 

3> python -m timeit -s "M=range(1000);f=lambda x:x*2" "L=[f(m) for m in M]" 
1000 loops, best of 3: 483 usec per loop 

4> python -m timeit -s "L=[]; A=L.append; M=range(1000)" "for m in M: A(m*2)" 
1000 loops, best of 3: 287 usec per loop  

5> python -m timeit -s "M=range(1000)" "L=[m*2 for m in M]" 
1000 loops, best of 3: 174 usec per loop 

Tenga en cuenta que son todos similares excepto los dos últimos. Son las llamadas a la función (L.append o f (x)) las que afectan severamente el tiempo. En el n. ° 4, la búsqueda de L.append se realizó una vez en la configuración. En el n. ° 5, se utiliza una lista de comps sin llamadas a funciones.

+0

Creo que se está refiriendo a mi publicación. Sí, encontré el grave problema de que 'map' devuelve iteradores en py3k, pero no creo que haya nada de malo en' timeit', 'range' devuelve iteradores por lo que hay poco impacto y no lo coloca en la fase de configuración. – iamamac

+0

> python3 -m timeit "[m para m en el rango (1000)]" 10000 bucles, mejor de 3: 114 usec por bucle > python3 -m timeit -s M = list (range (1000)) "[ m para m en M] " 10000 bucles, mejor de 3: 83 usec por bucle Hay una diferencia significativa en la creación de la lista solo una vez. –

Cuestiones relacionadas