¿Por qué la comprensión de listas de Python es rápida?
Hablemos primero de la derivación de listas. Los siguientes son los resultados de mi prueba en ipython (entorno de prueba Python 2.7.10):
gt gt gtlong_list = range(1000)
gt gt gta = []
gt gt gt cronometrarlo para I en la lista _ larga: a . append(I 1)
¿10000 bucle, preferiblemente 3:100? s por bucle
gt gt gt time it[I 1 for I in long _ list]
10000 bucles, mejores 3: 43,3? Cada bucle s
Se puede ver que la derivación de listas es aún más rápida que el bucle for.
Entonces, ¿por qué la comprensión de listas es rápida? Llamemos directamente al módulo dis de Python para ver su código de bytes:
Este es el código de bytes para la línea de código derivada de la lista:
0 genera la lista 0
p>3 LOAD_GLOBAL 0 (lista larga)
6 a ITER
gt gt7 a _ITER 16 (a 26)
10 STORE_FAST 0 (i) p>
13 LOAD_FAST 0 (i)
16 carga constante 1 (1)
19 BINARY_ADDITION
20 LIST_ Append 2
23 Jump_Absolute 7
...
Este es el código de bytes de la línea del bucle for:
6 SETUP_LOOP 31 (a 40)
9 LOAD_GLOBAL 0 (lista larga)
12 a ITER
gt gt13 a _ITER 23 (a 39)
16 STORE_FAST 1 (i)
19 LOAD_FAST 0 (a)
22 LOAD_ATTR 1 (añadir)
25 LOAD_FAST 1 (i)
28 Carga constante 1 ( 1)
31 Binary_Addition
32 CALL_FUNCTION 1
35 POP_TOP
36 Jump_Absolute 13
...
No es difícil descubrir que el proceso de derivación de listas y el bucle for son casi los mismos, excepto cómo agregarlos. Por lo tanto, no es imposible para usted decir que es azúcar de sintaxis...
Existe
En la derivación de listas, el código de bytes 'list_append' se usa directamente para implementar la función de agregar. , que es bastante eficiente. En un bucle for, cada bucle debe cargarse primero.
Agregue este atributo y luego agregue "call_function". Definitivamente esto será mucho más lento. Para probar nuestra conjetura, almacenamos la función append
en una variable local:
gt gt gta = []
gt gt gt call= a.append
gt gt gt cronometrarlo para I en la larga _ lista: invocar(I 1)
10000 bucles, mejores 3: ¿67,2? Cada bucle s
¿Descubrió que es casi 40 veces más rápido que la versión anterior del bucle for, y el resto es solo más de 20 veces? El costo de s es, naturalmente, el costo de "llamar a la función".
Creo que deberías entender por qué la comprensión de listas es más rápida que el bucle for.
El secreto está en este código de bytes 'list_append', que equivale a llamar directamente a la versión en lenguaje C de la función (no precisa), omitiendo algunos pasos intermedios.
A continuación, hablemos brevemente del mapa. En términos generales, usar map directamente es más rápido que un bucle, pero a veces las cosas se ponen raras, por ejemplo:
gt gt gt time it for I in long _ list: a append(I 1)
10000 ciclos, ¿mejor 3:100? s por bucle gt gt gttimeit map(lambda x: x 1, long_list)
¿10000 bucles, preferiblemente 3:109? s por bucle
No te preocupes, cambiamos la escritura del mapa a esto:
gt gt gtint_object = 1
gt gt gttimeit mapeo (int_object.__add__ , long_list)
10000 bucles, 3: 41,6, ¿mejor? cada bucle
¡Entonces sucede algo mágico! ¡Básicamente tan rápido como una lista de comprensión! (⊙o⊙)
Esto se debe principalmente a lambda.
La función generada por la expresión es Python, pero la versión C se llama directamente usando el operador o el método __add__. Si se usa lambda para reemplazar en la derivación de la lista,
La velocidad de las dos expresiones es casi la misma y el mapa es generalmente más rápido. Esencialmente, map llama a la función C subyacente, por lo que es naturalmente rápido. En resumen, no es necesario.
El mapeo es más rápido cuando se usa lambda.