¿Qué tan (in) eficiente es una lista de comprensión si no la asigna?

En esta pregunta , tengo una discusión con un comentarista que argumenta que

for t in threads: t.join() 

seria mejor que

 [t.join() for t in threads] 

Dejando de lado la cuestión de “abusar de las comprensiones”, tiendo a estar de acuerdo, pero me gustaría una frase para esto: ¿Qué tan (in) eficiente es mi versión (la segunda) realmente? ¿Python materializa la lista de comprensión siempre / en mi caso o utiliza un generador internamente?

¿Sería más eficiente el map(lambda t: t.join(), threads) ? ¿O hay otra forma de aplicar la función a cada elemento de la lista de threads ?

Una comprensión de lista siempre producirá un objeto de lista, en este caso con los valores de retorno de todas las llamadas t.join() . Python, por lo tanto, produce una lista con valores de None de longitud len(threads) para usted. Python nunca intentará optimizar la creación de objetos de la lista.

El uso de map() tampoco es más eficiente a medida que agrega empujones de stack adicionales con la lambda . Sólo quédate con lo explícito for bucle.

Realmente, para una serie de uniones de hilos no tiene sentido intentar micro optimizar aquí. Está dañando la legibilidad de un código no crítico.

En otras palabras, estoy totalmente de acuerdo con el comentarista. No use una lista de comprensión o map() solo para los efectos secundarios y evite tener que presionar ENTER y crear dos líneas de código.

Citando el Zen de Python :

  • La legibilidad cuenta.

Si desea una sola línea para esto, escriba una función de escape y úsela junto con una expresión generadora:

 def exhaust(iterator): for _ in iterator: pass exhaust(t.join() for t in threads) 

Entonces no está pagando el costo de todo el almacenamiento de la lista.

Siéntase libre de cambiar el nombre de algo más rápido o relevante para su uso.

He visto este abuso de comprensión de listas bastante, incluso en las entrevistas que codifican muestras en las que hemos instruido explícitamente a los candidatos para que eviten el crecimiento ilimitado de la memoria al resolver el problema.

Puedes hacer un foreach que funcione como quieras. Lo recomendaría, ya que no es una forma normal de hacer las cosas en Python. Sin probar, pero en la línea de esto:

 class foreach: def __init__(self, object_list): self.__object_list = object_list def __getattr__(self, name): def f(*args, **kwargs): for obj in self.__object_list: getattr(obj, name)(*args, **kwargs) return f foreach(thread_list).join() 

Prácticamente crea una especie de objeto proxy que reenvía cualquier llamada con cualquier parámetro a todos los objetos.

(Lo más probable es que tenga una buena conversación si lo hace en mi equipo de desarrollo, pero sirve como un ejemplo de lo que es posible en Python)

Si quieres una sola línea, ¿por qué no solo for t in threads: t.join() ?

Parece la solución más simple para mí, y es probable que también sea la más rápida (aunque en realidad, la sobrecarga de unir hilos es probable que empequeñezca cualquier otra cosa IMO)