Lista de comprensión vs mapa

837

¿Hay alguna razón para preferir usar la map()comprensión de listas o viceversa? ¿Alguno de ellos es generalmente más eficiente o se considera generalmente más pitónico que el otro?

3
  • 8
    Tenga en cuenta que PyLint advierte si usa mapa en lugar de comprensión de lista, vea el mensaje W0141 . lumbric 31/10/2013 a las 10:37
  • 3
    @lumbric, no estoy seguro, pero solo lo hace si se usa lambda en el mapa. 0xc0de 23 de mayo de 2017 a las 4:53
  • 2
    Hice un tutorial de 17 minutos sobre la composición de la lista vs el mapa si alguien lo encuentra útil - youtube.com/watch?v=hNW6Tbp59HQBrendan Metcalfe 1 ago.20 a las 16:08
748

mappuede ser microscópicamente más rápido en algunos casos (cuando NO está haciendo una lambda para el propósito, pero usando la misma función en el mapa y una lista de compilación). Las comprensiones de listas pueden ser más rápidas en otros casos y la mayoría (no todos) los pythonistas las consideran más directas y claras.

Un ejemplo de la pequeña ventaja de velocidad del mapa cuando se usa exactamente la misma función:

$ python -m timeit -s'xs=range(10)' 'map(hex, xs)'
100000 loops, best of 3: 4.86 usec per loop
$ python -m timeit -s'xs=range(10)' '[hex(x) for x in xs]'
100000 loops, best of 3: 5.58 usec per loop

Un ejemplo de cómo la comparación de rendimiento se invierte por completo cuando el mapa necesita una lambda:

$ python -m timeit -s'xs=range(10)' 'map(lambda x: x+2, xs)'
100000 loops, best of 3: 4.24 usec per loop
$ python -m timeit -s'xs=range(10)' '[x+2 for x in xs]'
100000 loops, best of 3: 2.32 usec per loop
18
  • 52
    Sí, de hecho, nuestra guía de estilo interna de Python en funcionamiento recomienda explícitamente las listas de comparación con el mapa y el filtro (sin mencionar el pequeño pero medible mapa de mejora del rendimiento que puede dar en algunos casos ;-). Alex Martelli 8 de agosto de 2009 a las 3:55
  • 51
    No quiero hablar de los infinitos puntos de estilo de Alex, pero a veces el mapa me parece más fácil de leer: data = map (str, some_list_of_objects). Algunos otros ... operator.attrgetter, operator.itemgetter, etc.Gregg Lind 8 de agosto de 2009 a las 16:06
  • 69
    map(operator.attrgetter('foo'), objs)más fácil de leer que [o.foo for o in objs]?! Alex Martelli 8 de agosto de 2009 a las 18:42
  • 58
    @Alex: Prefiero no introducir nombres innecesarios, como oaquí, y tus ejemplos muestran por qué. Reid Barton 22/01/10 a las 20:38
  • 33
    Sin embargo, creo que @GreggLind tiene razón, con su str()ejemplo. Eric O Lebigot 5/10/11 a las 7:55
523

Casos

  • Caso común : casi siempre, querrá usar una lista de comprensión en Python porque será más obvio lo que está haciendo para los programadores novatos que leen su código. (Esto no se aplica a otros lenguajes, donde pueden aplicarse otros modismos.) Será incluso más obvio lo que está haciendo para los programadores de Python, ya que las listas por comprensión son el estándar de facto en Python para la iteración; se esperan .
  • Caso menos común : sin embargo, si ya tiene una función definida , a menudo es razonable usarla map, aunque se considera "no pitónica". Por ejemplo, map(sum, myLists)es más elegante / conciso que [sum(x) for x in myLists]. Obtiene la elegancia de no tener que inventar una variable ficticia (por ejemplo, sum(x) for x...o sum(_) for _...o sum(readableName) for readableName...) que tiene que escribir dos veces, solo para iterar. El mismo argumento es válido para filtery reducey cualquier cosa del itertoolsmódulo: si ya tiene una función a mano, puede seguir adelante y hacer algo de programación funcional. Esto gana legibilidad en algunas situaciones y la pierde en otras (por ejemplo, programadores novatos, múltiples argumentos) ... pero la legibilidad de su código depende en gran medida de sus comentarios de todos modos.
  • Casi nunca : es posible que desee utilizar la mapfunción como una función abstracta pura mientras realiza la programación funcional, donde está mapeando mapo cursando map, o beneficiarse de hablar mapcomo una función. En Haskell, por ejemplo, una interfaz de función denominada fmapgeneraliza el mapeo sobre cualquier estructura de datos. Esto es muy poco común en Python porque la gramática de Python te obliga a usar el estilo de generador para hablar sobre iteración; no se puede generalizar fácilmente. (Esto a veces es bueno ya veces malo). Probablemente pueda encontrar ejemplos raros de Python donde map(f, *lists)sea ​​razonable. El ejemplo más cercano que se me ocurre sería sumEach = partial(map,sum), que es una línea de una sola línea que es aproximadamente equivalente a:

def sumEach(myLists):
    return [sum(_) for _ in myLists]
  • Solo usando un for-loop : también puede, por supuesto, usar un for-loop. Si bien no es tan elegante desde el punto de vista de la programación funcional, a veces las variables no locales hacen que el código sea más claro en lenguajes de programación imperativos como Python, porque la gente está muy acostumbrada a leer código de esa manera. Los bucles for también son, en general, los más eficientes cuando simplemente está haciendo una operación compleja que no está construyendo una lista como la lista de comprensión y el mapa para los que están optimizados (por ejemplo, sumar o hacer un árbol, etc.), al menos eficiente en términos de memoria (no necesariamente en términos de tiempo, donde esperaría, en el peor de los casos, un factor constante, salvo algún raro hipo patológico en la recolección de basura).

"Pitonismo"

No me gusta la palabra "pitónico" porque no encuentro que pitón sea siempre elegante a mis ojos. Sin embargo, mapy filterfunciones similares (como el itertoolsmódulo muy útil ) probablemente se consideren poco pitónicas en términos de estilo.

pereza

En términos de eficiencia, como la mayoría de las construcciones de programación funcional, MAP PUEDE SER LAZY y, de hecho, es perezoso en Python. Eso significa que puede hacer esto (en python3 ) y su computadora no se quedará sin memoria y perderá todos sus datos no guardados:

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

Intente hacer eso con una lista de comprensión:

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

Tenga en cuenta que las listas por comprensión también son inherentemente perezosas, pero Python ha optado por implementarlas como no perezosas . Sin embargo, Python admite la comprensión de listas diferidas en forma de expresiones generadoras, de la siguiente manera:

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

Básicamente, puede pensar en la [...]sintaxis como pasar una expresión generadora al constructor de la lista, como list(x for x in range(5)).

Breve ejemplo artificial

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

Las comprensiones de listas no son perezosas, por lo que pueden requerir más memoria (a menos que use comprensiones del generador). Los corchetes a [...]menudo hacen que las cosas sean obvias, especialmente cuando hay un lío de paréntesis. Por otro lado, a veces terminas siendo tan prolijo como escribir [x for x in.... Siempre que mantenga sus variables de iterador cortas, las listas por comprensión suelen ser más claras si no aplica sangría a su código. Pero siempre puedes aplicar sangría a tu código.

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

o romper las cosas:

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

Comparación de eficiencia para python3

map ahora es perezoso:

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

Por tanto, si no vas a utilizar todos tus datos, o no sabes de antemano cuántos datos necesitas, mapen python3 (y expresiones generadoras en python2 o python3) evitarás calcular sus valores hasta el último momento necesario. Por lo general, esto generalmente superará los gastos generales derivados del uso map. La desventaja es que esto es muy limitado en Python a diferencia de la mayoría de los lenguajes funcionales: solo obtienes este beneficio si accedes a tus datos de izquierda a derecha "en orden", porque las expresiones del generador de Python solo se pueden evaluar en el orden x[0], x[1], x[2], ....

Sin embargo, digamos que tenemos una función prefabricada que fnos gustaría map, e ignoramos la pereza de mapforzar inmediatamente la evaluación con list(...). Obtenemos unos resultados muy interesantes:

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

Los resultados tienen el formato AAA / BBB / CCC, donde A se realizó en una estación de trabajo Intel de alrededor de 2010 con python 3.?.?, Y B y C se realizaron con una estación de trabajo AMD de alrededor de 2013 con python 3.2.1, con hardware extremadamente diferente. El resultado parece ser que las comprensiones de mapas y listas son comparables en rendimiento, lo que se ve más afectado por otros factores aleatorios. Lo único que podemos decir parece ser que, curiosamente, si bien esperamos que las listas por comprensión [...]funcionen mejor que las expresiones generadoras (...), mapTAMBIÉN es más eficiente que las expresiones generadoras (nuevamente asumiendo que todos los valores son evaluados / usados).

Es importante darse cuenta de que estas pruebas asumen una función muy simple (la función de identidad); sin embargo, esto está bien porque si la función fuera complicada, la sobrecarga de rendimiento sería insignificante en comparación con otros factores del programa. (Todavía puede ser interesante probar con otras cosas simples como f=lambda x:x+x)

Si tiene habilidad para leer el ensamblaje de Python, puede usar el dismódulo para ver si eso es realmente lo que está sucediendo detrás de escena:

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

Parece que es mejor usar [...]sintaxis que list(...). Lamentablemente, la mapclase es un poco opaca para el desmontaje, pero podemos cumplir con nuestra prueba de velocidad.

6
  • 5
    "El módulo itertools muy útil [es] probablemente considerado poco pitónico en términos de estilo". Mmm. Tampoco me gusta el término "Pythonic", así que en cierto sentido no me importa lo que significa, pero no creo que sea justo para quienes lo usan, decir que de acuerdo con las incorporaciones de "Pythonicness" mapy filterjunto con la biblioteca estándar itertoolsson intrínsecamente de mal estilo. A menos que GvR realmente diga que fue un error terrible o solo por el rendimiento, la única conclusión natural si eso es lo que dice "Pythonicness" es olvidarlo como estúpido ;-)Steve Jessop 13/0214 a las 17:53
  • 8
    @SteveJessop: En realidad, Guido pensó quemapfilter eliminar / era una gran idea para Python 3 , y solo una rebelión de otros Pythonistas los mantuvo en el espacio de nombres incorporado (mientras reducese movía a functools). Personalmente no estoy de acuerdo ( mapy estoy filterbien con las funciones predefinidas, particularmente integradas, simplemente nunca las use si lambdafuera necesario), pero GvR básicamente las ha llamado no Pythonic durante años. ShadowRanger 16 oct 2015 a las 20:43
  • @ShadowRanger: cierto, pero ¿alguna vez planeó GvR eliminarlo itertools? La parte que cito de esta respuesta es la afirmación principal que me desconcierta. No sé si en su mundo ideal, mapy me filtermovería a itertools(o functools) o iría por completo, pero cualquiera que sea el caso, una vez que uno dice que no itertoolses pitónico en su totalidad, entonces realmente no sé qué es "Pythonic". se supone que significa, pero no creo que pueda ser algo similar a "lo que GvR recomienda que la gente use". Steve Jessop 16/10/15 a las 21:39
  • 3
    @SteveJessop: Solo me estaba dirigiendo a map/ filter, no itertools. La programación funcional es perfectamente Pythonic ( itertools, functoolsy operatorfueron diseñados específicamente con la programación funcional en mente, y lo uso modismos funcionales en Python todo el tiempo), y itertoolsproporciona características que sería un dolor de aplicar a sí mismo, es específicamente mapy filterser redundante con las expresiones generadoras que hizo que Guido los odiara. itertoolssiempre ha estado bien. ShadowRanger 16 oct 2015 a las 23:05
  • 2
    Podría preferir esta respuesta si hubiera una manera. Bien explicado. NelsonGon 9/07/19 a las 12:22
106

Python 2: debe usar mapy en filterlugar de listas por comprensión.

Una razón objetiva por la que debería preferirlos aunque no sean "Pythonic" es esta:
requieren funciones / lambdas como argumentos, lo que introduce un nuevo alcance .

Me ha mordido esto más de una vez:

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

pero si en cambio hubiera dicho:

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

entonces todo hubiera estado bien.

Se podría decir que estaba siendo tonto por usar el mismo nombre de variable en el mismo ámbito.

Yo no lo estaba. El código estaba bien originalmente, los dos xno estaban en el mismo ámbito.
Fue solo después de que moví el bloque interno a una sección diferente del código que surgió el problema (léase: problema durante el mantenimiento, no durante el desarrollo), y no lo esperaba.

Sí, si nunca comete este error , las listas por comprensión son más elegantes.
Pero por experiencia personal (y por ver a otros cometer el mismo error) lo he visto suceder tantas veces que creo que no vale la pena el dolor por el que tienes que pasar cuando estos errores se introducen en tu código.

Conclusión:

Utilice mapy filter. Previenen errores sutiles relacionados con el osciloscopio y difíciles de diagnosticar.

Nota al margen:

¡No olvide considerar usar imapy ifilter(in itertools) si son apropiados para su situación!

14
  • 8
    Gracias por señalar esto. No se me había ocurrido explícitamente que la comprensión de la lista estaba en el mismo ámbito y podría ser un problema. Dicho esto, creo que algunas de las otras respuestas dejan en claro que la comprensión de listas debería ser el enfoque predeterminado la mayor parte del tiempo, pero que esto es algo para recordar. Este también es un buen recordatorio general para mantener las funciones (y, por lo tanto, el alcance) pequeñas y realizar pruebas unitarias exhaustivas y utilizar declaraciones de aserción. TimothyAWiseman 21/11/12 a las 19:00
  • 13
    @wim: Esto solo se trataba de Python 2, aunque se aplica a Python 3 si desea seguir siendo compatible con versiones anteriores. Lo sabía y había estado usando Python durante un tiempo (sí, más de unos pocos meses), y sin embargo, me pasó a mí. He visto a otros que son más inteligentes que yo caer en la misma trampa. Si eres tan brillante y / o experimentado que esto no es un problema para ti, entonces me alegro por ti, no creo que la mayoría de la gente sea como tú. Si lo fueran, no habría tanta necesidad de arreglarlo en Python 3.user541686 17 de diciembre de 2013 a las 23:48
  • 14
    Lo siento, pero escribiste esto a fines de 2012, mucho después de que Python 3 esté en escena, y la respuesta dice que estás recomendando un estilo de codificación de Python que de otro modo sería impopular solo porque te picó un error al cortar y ... pegar código. Nunca dije ser brillante o tener experiencia, simplemente no estoy de acuerdo en que la afirmación audaz esté justificada por tus razones. wim 18 dic '13 a las 0:14
  • 8
    @wim: ¿Eh? Python 2 todavía se usa en muchos lugares, el hecho de que Python 3 exista no cambia eso. Y cuando dices "no es exactamente un error sutil para cualquiera que haya usado Python más de unos pocos meses", esa oración significa literalmente "esto solo concierne a desarrolladores sin experiencia" (claramente no a ti). Y para que conste, claramente no leyó la respuesta porque dije en negrita que estaba moviendo , no copiando, el código. Los errores de copiar y pegar son bastante uniformes en todos los idiomas. Este tipo de error es más exclusivo de Python debido a su alcance; es más sutil y más fácil olvidarse y perderse. user541686 18 dic '13 a las 0:38
  • 3
    Todavía no es una razón lógica para cambiar ay map/ o filter. En todo caso, la traducción más directa y lógica para evitar su problema no es map(lambda x: x ** 2, numbers)a una expresión generadora list(x ** 2 for x in numbers)que no se filtre, como JeromeJ ya ha señalado. Mire Mehrdad, no tome un voto negativo tan personalmente, simplemente no estoy de acuerdo con su razonamiento aquí. wim 18 de diciembre de 2013 a las 1:39
48

En realidad, las mapcomprensiones de listas y listas se comportan de manera bastante diferente en el lenguaje Python 3. Eche un vistazo al siguiente programa de Python 3:

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

Puede esperar que imprima la línea "[1, 4, 9]" dos veces, pero en su lugar imprime "[1, 4, 9]" seguida de "[]". La primera vez que lo miras squaresparece comportarse como una secuencia de tres elementos, pero la segunda vez como uno vacío.

En Python 2, el lenguaje mapdevuelve una lista antigua simple, al igual que lo hacen las listas por comprensión en ambos lenguajes. El quid es que el valor de retorno de mapen Python 3 (y imapen Python 2) no es una lista, ¡es un iterador!

Los elementos se consumen cuando itera sobre un iterador a diferencia de cuando itera sobre una lista. Es por eso que se squaresve vacío en la última print(list(squares))línea.

Para resumir:

  • Cuando se trata de iteradores, debe recordar que tienen estado y que mutan a medida que los atraviesa.
  • Las listas son más predecibles ya que solo cambian cuando las mutas explícitamente; son contenedores .
  • Y una ventaja: los números, cadenas y tuplas son aún más predecibles ya que no pueden cambiar en absoluto; son valores .
4
  • este es probablemente el mejor argumento para la comprensión de listas. El mapa de pitones no es el mapa funcional, sino el hijastro pelirrojo lisiado de una implementación funcional. Muy triste, porque realmente me desagradan las comprensiones. semiomant 29/06/2017 a las 14:26
  • @semiomant Yo diría que el mapa perezoso (como en python3) es más 'funcional' que el mapa ansioso (como en python2). Por ejemplo, el mapa en Haskell es perezoso (bueno, todo en Haskell es perezoso ...). De todos modos, el mapa perezoso es mejor para encadenar mapas: si tiene un mapa aplicado al mapa aplicado al mapa, tiene una lista para cada una de las llamadas de mapa intermedias en python2, mientras que en python3 solo tiene una lista resultante, por lo que es más eficiente en memoria . MnZrK 21/10/2017 a las 15:57
  • Supongo que lo que quiero es mapproducir una estructura de datos, no un iterador. Pero tal vez los iteradores perezosos sean más fáciles que las estructuras de datos perezosas. Comida para el pensamiento. Gracias @MnZrKsemiomant 24 oct 2017 a las 11:01
  • Quiere decir que el mapa devuelve un iterable, no un iterador. user541686 18 de septiembre de 2018 a las 21:33
19

Si planea escribir cualquier código asincrónico, paralelo o distribuido, probablemente preferirá la mapcomprensión de una lista, ya que la mayoría de los paquetes asincrónicos, paralelos o distribuidos proporcionan una mapfunción para sobrecargar Python map. Luego, al pasar la mapfunción adecuada al resto de su código, es posible que no tenga que modificar su código de serie original para que se ejecute en paralelo (etc.).

2
18

Aquí hay un caso posible:

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

versus:

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

Supongo que zip () es una sobrecarga desafortunada e innecesaria que debe disfrutar si insiste en usar listas de comprensión en lugar del mapa. Sería genial si alguien aclarara esto ya sea afirmativa o negativamente.

9
  • "[op1 * op2 de op1, op2 en zip (list1, list2)]" | s / form / for / Y una lista equivalente sin zip: (menos legible) [list1 [i] * list2 [i] for i in range (len (list1))]weakish 9 de agosto de 2010 a las 2:45
  • 2
    Debería ser "para" no "de" en su segunda cita de código, @andz, y también en el comentario de @ debilish. Pensé que había descubierto un nuevo enfoque sintáctico para listas por comprensión ... Maldita sea. physicsmichael 12/10/10 a las 13:12
  • 4
    para agregar un comentario muy tardío, puede hacer zipperezoso usandoitertools.iziptacaswell 17/12/12 a las 21:09
  • 7
    Creo que todavía lo prefiero map(operator.mul, list1, list2). Es en estas expresiones muy simples del lado izquierdo donde las comprensiones se vuelven torpes. Yann Vernier 10/03/15 a las 23:34
  • 1
    No me había dado cuenta de que el mapa podría tomar varios iterables como entradas para su función y, por lo tanto, podría evitar un zip. bli 23 de enero de 2017 a las 11:20
dieciséis

Encuentro que las listas por comprensión son generalmente más expresivas de lo que estoy tratando de hacer que map: ambas lo hacen, pero la primera ahorra la carga mental de tratar de comprender lo que podría ser una lambdaexpresión compleja .

También hay una entrevista en algún lugar (no puedo encontrarla de inmediato) donde Guido enumera lambdasy las funciones funcionales como lo que más lamenta aceptar en Python, por lo que podría argumentar que no son Pythonic en virtud de eso.

4
  • 10
    Sí, suspiro, pero la intención original de Guido de eliminar lambda por completo en Python 3 recibió un aluvión de cabildeo en su contra, por lo que volvió a hacerlo a pesar de mi firme apoyo; bueno, supongo que lambda es demasiado útil en muchos casos SIMPLES , el único el problema es cuando excede los límites de SIMPLE o se le asigna un nombre (en cuyo último caso es un duplicado tonto y cojeando de def! -). Alex Martelli 8 de agosto de 2009 a las 3:58
  • 3
    La entrevista en la que estás pensando es esta: amk.ca/python/writing/gvr-interview , donde Guido dice "A veces he sido demasiado rápido para aceptar contribuciones y luego me di cuenta de que fue un error. Un ejemplo sería algunas de las características de programación funcional, como las funciones lambda. lambda es una palabra clave que le permite crear una pequeña función anónima; funciones integradas como map, filter y reduce ejecutan una función sobre un tipo de secuencia, como una lista. " J. Taylor 24 de marzo de 2011 a las 4:30
  • 4
    @Alex, no tengo sus años de experiencia, pero he visto listas por comprensión mucho más complicadas que lambdas. Por supuesto, abusar de las características del lenguaje siempre es una tentación difícil de resistir. Es interesante que las listas por comprensión (empíricamente) parecen más propensas al abuso que las lambdas, aunque no estoy seguro de por qué debería ser así. También señalaré que "cojear" no siempre es algo malo. Reducir el alcance de "cosas que podría estar haciendo esta línea" a veces puede facilitar las cosas al lector. Por ejemplo, la constpalabra clave en C ++ es un gran triunfo en este sentido. Stuart Berg 25 de marzo de 2013 a las 15:02
  • > guido. Lo cual es otra prueba de que Guido está loco. Por supuesto lambda, se han hecho tan poco convincentes (sin declaraciones ...) que son difíciles de usar y limitadas de todos modos. WestCoastProjects 6 de enero de 2019 a las 23:41
12

Entonces, dado que Python 3 map()es un iterador, debe tener en cuenta lo que necesita: un iterador u listobjeto.

Como ya mencionó @AlexMartelli , map()es más rápido que la comprensión de listas solo si no usa la lambdafunción.

Les presentaré algunas comparaciones de tiempo.

Python 3.5.2 y CPython
He usado el cuaderno Jupiter y especialmente el %timeitcomando mágico incorporado
Medidas : s == 1000 ms == 1000 * 1000 µs = 1000 * 1000 * 1000 ns

Configuración:

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

Función incorporada:

%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 función:

%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

También existe la expresión generadora, consulte PEP-0289 . Así que pensé que sería útil agregarlo a la comparación.

%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

Necesitas listobjeto:

Use la comprensión de la lista si es una función personalizada, use list(map())si hay una función incorporada

No necesita un listobjeto, solo necesita uno iterable:

¡Úsalo siempre map()!

2

Ejecuté una prueba rápida comparando tres métodos para invocar el método de un objeto. La diferencia de tiempo, en este caso, es insignificante y depende de la función en cuestión (ver la respuesta de @Alex Martelli ). Aquí, miré los siguientes métodos:

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

Miré listas (almacenadas en la variable vals) de números enteros (Python int) y números de punto flotante (Python float) para aumentar el tamaño de las listas. Se considera la siguiente clase ficticia DummyNum:

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

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

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

Específicamente, el addmétodo. El __slots__atributo es una optimización simple en Python para definir la memoria total que necesita la clase (atributos), reduciendo el tamaño de la memoria. Aquí están las gráficas resultantes.

Rendimiento del mapeo de métodos de objetos de Python

Como se indicó anteriormente, la técnica utilizada hace una diferencia mínima y debe codificar de la manera que sea más legible para usted, o en las circunstancias particulares. En este caso, la comprensión de listas ( map_comprehensiontécnica) es más rápida para ambos tipos de adiciones en un objeto, especialmente con listas más cortas.

Visite este pastebin para conocer la fuente utilizada para generar el gráfico y los datos.

3
  • 2
    Como ya se explicó en otras respuestas, mapes más rápido solo si la función se llama exactamente de la misma manera (es decir, [*map(f, vals)]vs. [f(x) for x in vals]). Entonces list(map(methodcaller("add"), vals))es más rápido que [methodcaller("add")(x) for x in vals]. mappuede que no sea más rápido cuando la contraparte en bucle utiliza un método de llamada diferente que puede evitar algunos gastos generales (por ejemplo, x.add()evita los methodcallergastos generales de expresión lambda o). Para este caso de prueba específico, [*map(DummyNum.add, vals)]sería más rápido (porque DummyNum.add(x)y x.add()tendría básicamente el mismo rendimiento). GZ0 31/07/19 a las 20:01
  • 1
    Por cierto, las list()llamadas explícitas son un poco más lentas que las listas por comprensión. Para una comparación justa, debe escribir [*map(...)]. GZ0 31/07/19 a las 20:01
  • @ GZ0 ¡gracias por los excelentes comentarios! Todo tiene sentido y no sabía que las list()llamadas aumentaban los gastos generales. Debería haber pasado más tiempo leyendo las respuestas. Volveré a ejecutar estas pruebas para una comparación justa, por insignificantes que sean las diferencias. craymichael 31/07/19 a las 20:29
1

Probé el código de @ alex-martelli pero encontré algunas discrepancias

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 toma la misma cantidad de tiempo incluso para rangos muy grandes, mientras que el uso de la comprensión de listas lleva mucho tiempo, como es evidente en mi código. Por lo tanto, aparte de ser considerado "no pitónico", no me he enfrentado a ningún problema de rendimiento relacionado con el uso del mapa.

2
  • 5
    Esta es una pregunta muy antigua, y es muy probable que la respuesta a la que se refiere se haya escrito en referencia a Python 2, donde mapdevuelve una lista. En Python 3, mapse evalúa de forma perezosa, por lo que simplemente llamar mapno calcula ninguno de los nuevos elementos de la lista, por lo que se obtienen tiempos tan cortos. kaya3 16 de diciembre de 2019 a las 4:52
  • Creo que estás usando Python 3.x Cuando hice esta pregunta, Python 3 se había lanzado recientemente y Python 2.x era en gran medida el estándar. TimothyAWiseman 16 dic 2019 a las 16:51
1

ingrese la descripción de la imagen aquí

Fuente de la imagen: Experfy

Puede ver por sí mismo cuál es mejor entre: Comprensión de listas y Función de mapa

(La comprensión de listas toma menos tiempo para procesar 1 millón de registros en comparación con una función de mapa)

¡Espero eso ayude! Buena suerte :)

0

Considero que la forma más pitónica es usar una lista de comprensión en lugar de mapy filter. La razón es que las listas por comprensión son más claras que mapy 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

Como puede ver, una comprensión no requiere lambdaexpresiones adicionales como las mapnecesidades. Además, una comprensión también permite filtrar fácilmente, mientras que maprequiere filterpermitir el filtrado.