Python: ¿Se evalúa la función de división varias veces en una lista de comprensión?

Hay algo que me he estado preguntando por un tiempo. ¿La división se ejecuta una o varias veces en esta lista de comprensión?

l = [line.split()[i] for i in indexes] 

Actualmente hago una lista de comprensiones como estas de esta manera:

 l = line.rstrip().split() l = [l for i in indexes] 

Pero no estoy seguro, si es necesario. Además de una respuesta de sí / no, definitivamente me gustaría saber cómo podría hacerlo yo mismo haciendo un perfil de CPU o leyendo alguna documentación. Gracias.

La expresión de la izquierda en una lista de comprensión se evalúa de nuevo para cada elemento, sí.

Si solo necesita una evaluación, debe hacer exactamente lo que hizo; Llámelo primero y guarde el resultado para reutilizarlo en la lista de comprensión.

De la lista muestra documentación :

En este caso, los elementos de la nueva lista son aquellos que se producirían considerando cada una de las cláusulas for o if un bloque, anidando de izquierda a derecha y evaluando la expresión para producir un elemento de lista cada vez que se alcanza el bloque más interno .

Énfasis mío.

También puede desmontar la comprensión de la lista utilizando la función dis.dis() :

 >>> import dis >>> dis.dis(compile('[line.split()[i] for i in indexes]', '', 'eval')) 1 0 BUILD_LIST 0 3 LOAD_NAME 0 (indexes) 6 GET_ITER >> 7 FOR_ITER 22 (to 32) 10 STORE_NAME 1 (i) 13 LOAD_NAME 2 (line) 16 LOAD_ATTR 3 (split) 19 CALL_FUNCTION 0 22 LOAD_NAME 1 (i) 25 BINARY_SUBSCR 26 LIST_APPEND 2 29 JUMP_ABSOLUTE 7 >> 32 RETURN_VALUE 

El FOR_ITER operación FOR_ITER inicia el bucle (con JUMP_ABSOLUTE cerrándolo), y cada vez que se LOAD_NAME line una LOAD_NAME line LOAD_ATTR split , LOAD_ATTR split CALL_FUNCTION y CALL_FUNCTION . En otras palabras, los bytecodes 13 a 19 implementan la parte line.split() , y se ejecutan cada vez a través del bucle, que se ejecuta desde los bytecodes 7 hasta el 29.

(Nota de Python 3: la lista de comprensión tiene su propio ámbito y deberá extraer el objeto de código de las constantes del objeto de código externo; dis.dis(compile('[line.split()[i] for i in indexes]', '', 'eval').co_consts[0]) ).

Agregaré que su ejemplo de LC es un reemplazo para:

 l = [] for i in indexes: l.append(line.split()[i]) 

Entonces, la respuesta es definitivamente Sí, se evalúa cada vez por iteración.

Como dice @Dalen, al razonar sobre una lista de comprensión, creo que puede esperar que se comporte como si hubiera hecho lo mismo sin una comprensión. @Martijn muestra cómo puede verificar esto mirando el ensamblaje.

Mi respuesta

  1. Proporciona un método más fácil para verificar el comportamiento usted mismo (en el sentido de que no necesita leer el ensamblaje).

  2. Muestra el comportamiento de una función en varios lugares dentro de la lista de comprensión (encontré esta pregunta cuando me preguntaba con qué frecuencia se llama una “función externa” (ver más abajo)).

Código:

 def inner_func(i): print('called inner') return i def outer_func(n): print('called outer') return range(n) l = [inner_func(i) for i in outer_func(5)] 

Esto se imprimirá una vez called outer y se called inner 5 veces, verificando que, al igual que en un bucle normal, la función externa se ejecuta una vez y la función interna una vez por bucle.