¿Hay alguna razón para preferir el uso de la map()
comprensión de la lista o viceversa? ¿Alguno de ellos es generalmente más eficiente o generalmente considerado más pitónico que el otro?
python
list-comprehension
map-function
TimothyAWiseman
fuente
fuente
Respuestas:
map
puede ser microscópicamente más rápido en algunos casos (cuando NO está haciendo una lambda para ese propósito, sino que usa la misma función en map y listcomp). Las comprensiones de listas pueden ser más rápidas en otros casos y la mayoría (no todos) los pitonistas 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:
Un ejemplo de cómo la comparación de rendimiento se revierte por completo cuando el mapa necesita una lambda:
fuente
map(operator.attrgetter('foo'), objs)
más fácil de leer que[o.foo for o in objs]
?o
aquí, y sus ejemplos muestran por qué.str()
ejemplo.Casos
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 componer una variable ficticia (por ejemplo,sum(x) for x...
orsum(_) for _...
osum(readableName) for readableName...
) que debe escribir dos veces, solo para iterar. El mismo argumento es válido parafilter
yreduce
todo lo relacionado con elitertools
módulo: si ya tiene una función a mano, puede seguir adelante y hacer una programación funcional. Esto aumenta la 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.map
función como una función abstracta pura mientras realiza la programación funcional, donde está mapeandomap
o cursandomap
, o de lo contrario se beneficia de hablarmap
como una función. En Haskell, por ejemplo, una interfaz de functor llamadafmap
generaliza el mapeo sobre cualquier estructura de datos. Esto es muy poco común en python porque la gramática de python lo obliga a usar el estilo generador para hablar sobre la iteración; no puedes generalizarlo fácilmente. (Esto a veces es bueno y a veces malo). Probablemente se te ocurran ejemplos raros de Python dondemap(f, *lists)
sea algo razonable. El ejemplo más cercano que se me ocurre seríasumEach = partial(map,sum)
, que es una línea que es más o menos equivalente a:for
bucle : También puede, por supuesto, usar un bucle for. Si bien no es tan elegante desde el punto de vista de la programación funcional, a veces las variables no locales aclaran el código en lenguajes de programación imperativos como python, porque las personas están muy acostumbradas a leer código de esa manera. Los bucles for también son, por lo general, los más eficientes cuando simplemente realiza una operación compleja que no es construir una lista, como la comprensión de listas 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 algunos hipo patológicos raros de recolección de basura)."Pitonismo"
No me gusta la palabra "pitón" porque no encuentro que pitón sea siempre elegante en mis ojos. Sin embargo,
map
yfilter
funciones similares (como elitertools
módulo muy útil ) probablemente se consideren poco pitónicas en términos de estilo.pereza
En términos de eficiencia, al igual que la mayoría de las construcciones de programación funcional, MAP PUEDE SER PEREZOSO , 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:
Intenta hacerlo con una lista de comprensión:
Tenga en cuenta que las comprensiones de listas también son inherentemente perezosas, pero Python ha elegido implementarlas como no perezosas . Sin embargo, python admite comprensiones de listas perezosas en forma de expresiones generadoras, de la siguiente manera:
Básicamente puedes pensar en el
[...]
sintaxis como pasar una expresión de generador al constructor de la lista, comolist(x for x in range(5))
.Breve ejemplo artificial
Las comprensiones de listas no son perezosas, por lo que puede requerir más memoria (a menos que use comprensiones de generador). Los corchetes a
[...]
menudo hacen que las cosas sean obvias, especialmente cuando están entre paréntesis. Por otro lado, a veces terminas siendo detallado como escribir[x for x in...
. Siempre y cuando mantenga las variables de su iterador cortas, las comprensiones de listas generalmente son más claras si no sangra su código. Pero siempre puedes sangrar tu código.o romper las cosas:
Comparación de eficiencia para python3
map
ahora es vago:Por lo tanto, si no va a utilizar todos sus datos, o no sabe de antemano cuántos datos necesita,
map
en python3 (y las expresiones generadoras en python2 o python3) evitará calcular sus valores hasta el último momento necesario. Por lo general, esto generalmente superará cualquier sobrecarga del usomap
. La desventaja es que esto es muy limitado en Python en comparación con 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 pueden evaluarse en el ordenx[0], x[1], x[2], ...
.Sin embargo, digamos que tenemos una función prefabricada que
f
nos gustaríamap
, e ignoramos la pereza demap
forzar inmediatamente la evaluación conlist(...)
. Obtenemos algunos resultados muy interesantes:Los resultados están en la forma AAA / BBB / CCC donde A se realizó con 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, mientras esperamos que las comprensiones de listas
[...]
funcionen mejor que las expresiones generadoras(...)
,map
TAMBIÉN es más eficiente que las expresiones generadoras (suponiendo nuevamente que todos los valores se evalúen / usen).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 en el programa. (Todavía puede ser interesante probar con otras cosas simples como
f=lambda x:x+x
)Si eres hábil para leer el ensamblaje de Python, puedes usar el
dis
módulo para ver si eso es lo que está sucediendo detrás de escena:Parece que es mejor usar la
[...]
sintaxis quelist(...)
. Lamentablemente, lamap
clase es un poco opaca para el desmontaje, pero podemos hacer las cosas con nuestra prueba de velocidad.fuente
map
yfilter
junto con la biblioteca estándaritertools
son inherentemente mal estilo. A menos que GvR realmente diga que fueron un terrible error o únicamente por el rendimiento, la única conclusión natural si eso es lo que dice "Pythonicness" es olvidarse de eso como estúpido ;-)map
/filter
era una gran idea para Python 3 , y solo una rebelión de otros Pythonistas los mantuvo en el espacio de nombres incorporado (mientrasreduce
se movía afunctools
). Personalmente, no estoy de acuerdo (map
y estoy de acuerdofilter
con las funciones predefinidas, particularmente incorporadas, simplemente nunca las use silambda
fuera necesario), pero GvR básicamente las ha llamado no Pythonic durante años.itertools
? La parte que cito de esta respuesta es la afirmación principal que me confunde. No sé si en su mundo ideal,map
y mefilter
mudaría aitertools
(ofunctools
) o iría por completo, pero cualquiera que sea el caso, una vez que uno diga que noitertools
es Phythonic 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".map
/filter
, noitertools
. La programación funcional es perfectamente Pythonic (itertools
,functools
yoperator
fueron diseñados específicamente con la programación funcional en mente, y lo uso modismos funcionales en Python todo el tiempo), yitertools
proporciona características que sería un dolor de aplicar a sí mismo, es específicamentemap
yfilter
ser redundante con las expresiones generadoras eso hizo que Guido los odiara.itertools
siempre ha estado bienPython 2: debes usar
map
yfilter
lugar de enumerar las comprensiones.Una razón objetiva por la que debería preferirlos aunque no sean "Pythonic" es esta:
requieren funciones / lambdas como argumentos, que introducen un nuevo alcance .
He sido mordido por esto más de una vez:
pero si en cambio hubiera dicho:
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 estaba El código estaba bien originalmente: los dos
x
s no estaban en el mismo alcance.Fue solo después de que me mudé el bloque interno a una sección diferente del código que surgió el problema (léase: problema durante el mantenimiento, no el desarrollo), y no lo esperaba.
Sí, si nunca comete este error, entonces las comprensiones de listas son más elegantes.
Pero por experiencia personal (y al ver a otros cometer el mismo error) lo he visto suceder tantas veces que creo que no vale la pena el dolor que tienes que pasar cuando estos errores se introducen en tu código.
Conclusión:
Uso
map
yfilter
. Previenen sutiles errores difíciles de diagnosticar relacionados con el alcance.Nota al margen:
¡No olvides considerar usar
imap
yifilter
(initertools
) si son apropiados para tu situación!fuente
map
y / ofilter
. En todo caso, la traducción más directa y lógica para evitar su problema no es hacerlo,map(lambda x: x ** 2, numbers)
sino más bien a una expresión generadoralist(x ** 2 for x in numbers)
que no se filtre, como ya señaló JeromeJ. Mira, Mehrdad, no tomes un voto a favor tan personalmente, simplemente estoy totalmente en desacuerdo con tu razonamiento aquí.En realidad,
map
y las comprensiones de listas se comportan de manera bastante diferente en el lenguaje Python 3. Eche un vistazo al siguiente programa de Python 3:Puede esperar que imprima la línea "[1, 4, 9]" dos veces, pero en su lugar imprime "[1, 4, 9]" seguido de "[]". La primera vez que miras
squares
parece comportarse como una secuencia de tres elementos, pero la segunda como vacía.En el lenguaje Python 2, se
map
devuelve una lista antigua simple, tal como lo hacen las comprensiones de listas en ambos idiomas. El quid es que el valor de retorno demap
Python 3 (yimap
en 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
squares
ve vacío en la últimaprint(list(squares))
línea.Para resumir:
fuente
map
producir una estructura de datos, no un iterador. Pero quizás los iteradores perezosos son más fáciles que las estructuras de datos perezosas. Comida para el pensamiento. Gracias @MnZrKEncuentro que las comprensiones de listas 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 entender lo que podría ser un complejolambda
expresión .También hay una entrevista en algún lugar (no puedo encontrarlo de manera informal) donde Guido enumera
lambda
las funciones funcionales como lo que más lamenta de aceptar en Python, por lo que podría argumentar que no son Pythonic en virtud de eso.fuente
const
palabra clave en C ++ es un gran triunfo en este sentido.lambda
, se han hecho tan lamentables (sin declaraciones ...) que son difíciles de usar y de todos modos limitados.Aquí hay un caso posible:
versus:
Supongo que el zip () es una sobrecarga desafortunada e innecesaria que debes disfrutar si insistes en usar listas de comprensión en lugar del mapa. Sería genial si alguien aclara esto ya sea afirmativa o negativamente.
fuente
zip
perezoso usandoitertools.izip
map(operator.mul, list1, list2)
. Es en estas simples expresiones del lado izquierdo que las comprensiones se vuelven torpes.Si planea escribir cualquier código asincrónico, paralelo o distribuido, probablemente preferirá
map
una comprensión de la lista, ya que la mayoría de los paquetes asincrónicos, paralelos o distribuidos proporcionan unamap
función para sobrecargar Pythonmap
. Luego, al pasar lamap
funció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.).fuente
Entonces, dado que Python 3
map()
es un iterador, debe tener en cuenta lo que necesita: un iterador ulist
objeto.Como @AlexMartelli ya mencionó ,
map()
es más rápido que la comprensión de la lista solo si no usa lalambda
función.Te presentaré algunas comparaciones de tiempo.
Python 3.5.2 y CPython
que he usado Júpiter portátil y especialmente
%timeit
comando integrado mágicasmediciones : s == 1000 ms == 1000 * 1000 µs = 1000 * 1000 * 1000 ns
Preparar:
Función incorporada:
lambda
función:También existe la expresión generador, ver PEP-0289 . Entonces pensé que sería útil agregarlo a la comparación
Necesitas
list
objeto:Use la comprensión de la lista si es una función personalizada, use
list(map())
úsela si hay una función incorporadaNo necesitas
list
objeto, solo necesita uno iterable:Siempre uso
map()
!fuente
Realicé una prueba rápida comparando tres métodos para invocar el método de un objeto. La diferencia horaria, 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:
Miré las listas (almacenadas en la variable
vals
) de números enteros (Pythonint
) y números de coma flotante (Pythonfloat
) para aumentar el tamaño de las listas. Se considera la siguiente clase ficticiaDummyNum
:Específicamente, el
add
mé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 parcelas resultantes.Como se indicó anteriormente, la técnica utilizada hace una diferencia mínima y debe codificar de la manera más legible para usted, o en la circunstancia particular. En este caso, la lista de comprensión (
map_comprehension
té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 diagrama y los datos.
fuente
map
es 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]
). Entonceslist(map(methodcaller("add"), vals))
es más rápido que[methodcaller("add")(x) for x in vals]
.map
Es posible 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 lamethodcaller
sobrecarga de la expresión o lambda). Para este caso de prueba específico,[*map(DummyNum.add, vals)]
sería más rápido (porqueDummyNum.add(x)
yx.add()
básicamente tendría el mismo rendimiento).list()
llamadas explícitas son un poco más lentas que las listas de comprensión. Para una comparación justa necesitas escribir[*map(...)]
.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.Considero que la forma más pitónica es utilizar una lista de comprensión en lugar de
map
yfilter
. La razón es que las comprensiones de listas son más claras quemap
yfilter
.Como puede ver, una comprensión no requiere
lambda
expresiones adicionales comomap
necesidades. Además, una comprensión también permite filtrar fácilmente, mientras quemap
requierefilter
permitir el filtrado.fuente
Probé el código por @ alex-martelli pero encontré algunas discrepancias
El mapa toma la misma cantidad de tiempo incluso para rangos muy grandes, mientras que el uso de la comprensión de la lista lleva mucho tiempo, como lo demuestra mi código. Por lo tanto, aparte de ser considerado "antipático", no he enfrentado ningún problema de rendimiento relacionado con el uso del mapa.
fuente
map
devuelve una lista. En Python 3,map
se evalúa de manera perezosa, por lo que simplemente llamarmap
no calcula ninguno de los nuevos elementos de la lista, por lo tanto, por qué tiene tiempos tan cortos.