Pregunta

¿Hay una razón para preferir el uso de más de map() lista por comprensión o viceversa? Es cualquiera de ellos en general más eficientes o se considera generalmente más Pythonic que el otro?

¿Fue útil?

Solución

map puede ser microscópicamente más rápido en algunos casos (cuando no se está haciendo una lambda para el propósito, pero utilizando la misma función en un mapa y listcomp). Las listas por comprensión puede ser más rápida que en otros casos y la mayoría (no todos) Pythonistas consideran más directa y clara.

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

$ 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 ejemplo de cómo la comparación de funcionamiento se invierte completamente cuando se necesita un mapa 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

Otros consejos

Protectores

  • caso común : Casi siempre, tendrá que utilizar una lista de comprensión pitón , ya que será más evidente lo que está haciendo para programadores novatos leer su código . (Esto no se aplica a otros idiomas, en los que se pueden aplicar otras expresiones idiomáticas.) Será incluso más evidente lo que está haciendo a los programadores de Python, ya que las listas por comprensión son el estándar de facto en Python para la iteración; que son esperada .
  • caso menos común : Sin embargo, si ya tienen una función definida , a menudo es razonable utilizar map, aunque se considera 'unpythonic'. Por ejemplo, es más elegante map(sum, myLists) / terso que [sum(x) for x in myLists]. Se gana la elegancia de no tener que compensar una variable ficticia (por ejemplo sum(x) for x... o sum(_) for _... o sum(readableName) for readableName...), que tiene que escribir dos veces, sólo para repetir. El mismo argumento es válido para filter y reduce y cualquier cosa desde el módulo itertools: si ya tiene una función muy útil, se puede seguir adelante y hacer un poco de programación funcional. Este gana la legibilidad en algunas situaciones, y lo pierde en otros (por ejemplo, los programadores novatos, múltiples argumentos) ... pero la legibilidad del código depende altamente de sus comentarios de todos modos.
  • Casi nunca : Es posible que desee utilizar la función map en función abstracto puro mientras se hace la programación funcional, en el que se aplica la relación map o currying map, o de otra manera beneficiarse de una conversación sobre como map Una función. En Haskell por ejemplo, una interfaz de funtor llamado fmap generaliza mapeo sobre cualquier estructura de datos. Esto es muy poco común en Python, porque la gramática Python que obliga a utilizar el generador de estilo para hablar de iteración; no se puede generalizar fácilmente. (Esto a veces es bueno ya veces malo.) Es probable que pueda encontrar ejemplos raros donde pitón map(f, *lists) es una cosa razonable para hacerlo. El ejemplo más cercano que puedo llegar a se sumEach = partial(map,sum), que es una sola línea que es muy más o menos equivalente a:

def sumEach(myLists):
    return [sum(_) for _ in myLists]
  • Sólo usando un bucle for : Puede también, por supuesto, sólo tiene que utilizar un bucle for. Aunque no es tan elegante desde el punto de vista de programación funcional, las variables a veces no locales hacen código más claro en lenguajes de programación imperativos tales como pitón, porque las personas están muy acostumbrados a leer el código de esa manera. Para bucles también son, en general, el más eficiente cuando se está haciendo más que cualquier operación compleja que no está construyendo una lista como list-comprensiones y mapa están optimizados para (por ejemplo, suma, o hacer un árbol, etc.) - por lo menos eficiente en términos de memoria (no necesariamente en términos de tiempo, donde yo esperaría que en el peor, un factor constante, salvo algunas raras patológica hipo-recolección de basura).

"Pythonism"

No me gusta la palabra "Pythonic" porque no me parece que Pythonic es siempre elegante en los ojos. Sin embargo, map y filter y funciones similares (como el módulo itertools muy útil) son probablemente considerados unpythonic en términos de estilo.

La pereza

En términos de eficiencia, como la mayoría de construcciones de programación funcional, mapa puede ser perezoso , y de hecho es perezoso en Python. Eso significa que usted puede hacer esto (en python3 ) y el equipo no se quede sin memoria y perder todos sus datos no guardados:

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

Trate de hacer eso con una lista por comprensión:

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

Ten en cuenta que las listas por comprensión también son inherentemente lento, pero Python ha optado por implementar como no perezoso . Sin embargo, pitón es compatible con listas por comprensión de descanso bajo la forma de expr generadorESIONES, como sigue:

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

Básicamente puede pensar en la sintaxis [...] como pasa en una expresión de generador a la lista de constructor, 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 listas por comprensión no son perezosos, por lo que pueden requerir más memoria (a menos que utilice comprensiones del generador). Los corchetes [...] a menudo hacen cosas evidentes, sobre todo cuando está en un lío de paréntesis. Por otro lado, a veces se termina de manera ampliada similar a hacerlo [x for x in.... Como siempre y cuando mantenga sus variables iterador resumen, las listas por comprensión son por lo general más clara si no indentar código. Pero siempre se puede sangrar el 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 la eficiencia de python3

map ahora es perezosa:

% 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 lo tanto, si usted no va a utilizar todos sus datos, o no se sabe de antemano la cantidad de datos que necesita, map en python3 (y expresiones de generadores en python2 o python3) evitará el cálculo de sus valores hasta el último momento necesario. Por lo general, esto por lo general mayores que cualquier sobrecarga del uso de map. La desventaja es que esto es muy limitado en Python en lugar de idiomas más funcionales:. Que sólo recibe este beneficio si tiene acceso a sus datos de izquierda a derecha "el fin", porque las expresiones generadoras pitón sólo pueden ser evaluados el x[0], x[1], x[2], ... orden

Sin embargo digamos que tenemos un f función de pre-hechos nos gustaría map, y dejamos de lado la pereza de map forzando inmediatamente con la evaluación 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>)

En los resultados se presentan en forma de AAA / BBB / CCC donde A se realizó con un Intel estación de trabajo alrededor del año-2010 con Python 3.?.?, Y B y C se realizaron con un AMD estación de trabajo alrededor de 2013 con Python 3.2 0,1, con un hardware muy diferente. El resultado parece ser que los mapas y las listas por comprensión son comparables en el rendimiento, que está más fuertemente afectado por otros factores aleatorios. Lo único que podemos decir parece ser que, por extraño que, mientras esperamos las listas por comprensión [...] un mejor desempeño que las expresiones generadoras (...), map también es más eficiente que las expresiones del generador (suponiendo de nuevo que todos los valores son evaluados / utilizados).

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

Si usted es experto en la asamblea pitón lectura, se puede utilizar el módulo de dis para ver si eso es realmente lo que está pasando detrás de las escenas:

>>> 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 la sintaxis de [...] list(...). Lamentablemente la clase map es un poco opaco para el desmontaje, pero podemos hacer debido a nuestra prueba de velocidad.

Python 2: Debe utilizar map y filter en lugar de las listas por comprensión

.

Un Objetivo razón por la cual se debe preferir ellos a pesar de que no son "Pythonic" es la siguiente:
Requieren funciones / lambdas como argumentos, que introducir un nuevo ámbito de aplicación .

He conseguido picaron 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 lugar de eso había dicho:

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

entonces todo habría estado bien.

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

No estaba. El código originalmente estaba bien - los dos xs no estaban en el mismo ámbito
. Fue sólo después de I trasladó el bloque interno a una sección diferente del código que el problema ocurrió. (Léase: un problema durante el mantenimiento, no el desarrollo), y yo no lo esperaba

Sí, si nunca hace este error a continuación son listas por comprensión más elegante.
Pero a partir de la experiencia personal (y de ver que otros hacen el mismo error) que he visto suceder tantas veces que creo que no vale la pena el dolor que tiene que pasar cuando estos insectos se meten en su código.

Conclusión:

Uso map y filter. Evitan que los errores relacionados con el ámbito difíciles de diagnosticar sutiles.

Nota al margen:

No se olvide de considerar el uso de imap y ifilter (en itertools) si son apropiados para su situación!

En realidad, map y listas por comprensión se comportan de manera muy diferente en el lenguaje Python 3. Echar un vistazo a la siguiente programa en Python 3:

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

Se podría esperar que imprima la línea "[1, 4, 9]" dos veces, pero en su lugar se imprime "[1, 4, 9]" seguido de "[]". La primera vez que se mire squares parece comportarse como una secuencia de tres elementos, pero la segunda vez como una vacía.

En el map lenguaje Python 2 devuelve una lista simple y llano, al igual que lo hacen las listas por comprensión en ambos idiomas. El punto crucial es que el valor de retorno de map en Python 3 (y imap en Python 2) no es una lista - que es un iterador

Los elementos se consumen cuando iterar sobre un iterador a diferencia de cuando iterar sobre una lista. Esta es la razón por squares parece vacío en la última línea print(list(squares)).

Para resumir:

  • Cuando se trata de iteradores que hay que recordar que son de estado y que mutan a medida que los atraviesan.
  • Las listas son más predecibles, ya que sólo cambian cuando mutar de forma explícita; que son contenedores .
  • y un bono de: números, cadenas y las tuplas son aún más predecible ya que no pueden cambiar en absoluto; que son valores .

Me parece listas por comprensión son generalmente más expresiva de lo que estoy tratando de hacer que map - ambos lograr que se haga, pero el primero salva la carga mental de tratar de entender lo que podría ser una expresión compleja lambda

También hay una entrevista en alguna parte (no puedo encontrar de improviso), donde Guido enumera lambdas y las funciones funcionales como lo que más lamenta de aceptar en Python, por lo que podría hacer que el argumento de que son in- Pythonic en virtud de que.

Este es un caso posible:

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

frente a:

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

Estoy adivinando el zip () es una sobrecarga desafortunado e innecesario que necesita para disfrutar de si usted insiste en el uso de listas por comprensión en lugar del mapa. Sería genial si alguien lo aclara si afirmativa o negativamente.

Si planea escribir ningún asíncrono, paralelo o código distribuido, es probable que prefiera map más de una lista por comprensión - como la mayoría asíncrono, paralelo o paquetes distribuidos proporcionan una función map sobrecargar map de pitón. Luego de pasar por la función map apropiado para el resto de su código, puede que no tenga que modificar su código de serie original para que se ejecute en paralelo (etc).

Así que desde Python 3, map() es un iterador, se que tener en cuenta lo que necesita hacer:. un objeto iterador o list

Como ya se @AlexMartelli mencionó , map() es más rápida que la lista de comprensión sólo si no se utiliza la función lambda.

Voy a presentar algunas comparaciones en el tiempo.

Python 3.5.2 y CPython
He usado Júpiter portátil y especialmente %timeit comando integrado mágica
mediciones : s == == 1000 ms 1000 * 1000 mu 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

Función 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

También hay tal cosa como la expresión del generador, consulte PEP- 0289 . Así que pensé que sería útil para añadirlo 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

Se necesitan objeto list:

Usar lista por comprensión si es función personalizada, el uso list(map()) si hay función incorporada

No necesitan objeto list, sólo tiene iterables uno:

Siempre use map()!

Considero que la forma más Pythonic es utilizar una lista por comprensión en lugar de map y filter. La razón es que las listas por comprensión son más claras que map y 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 se ve una, una comprensión no requiere expresiones lambda adicionales como map necesidades. Además, una comprensión también permite el filtrado fácilmente, mientras que map requiere filter para permitir el filtrado.

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

# 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 (almacenados en la vals variable) de dos números enteros (int Python) y números de punto flotante (float Python) para aumentar el tamaño de la lista. La siguiente clase DummyNum maniquí se considera:

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

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

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

Específicamente, el método add. El atributo __slots__ es una optimización simple en Python para definir la memoria total que necesitan los atributos de clase (), la reducción de tamaño de la memoria. Aquí están las parcelas resultantes.

funcionamiento de los métodos objeto de asignación de Python

Como se dijo anteriormente, la técnica utilizada hace una diferencia mínima y se debe codificar de una manera que es más legible a usted, o en la circunstancia particular. En este caso, la lista de comprensión (técnica map_comprehension) es más rápido para ambos tipos de adiciones en un objeto, especialmente con listas más cortas.

Visita este pastebin para la fuente utilizada para generar la trama y datos.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top