Pregunta lista de comprensión vs. lambda + filtro


Me encontré con una necesidad básica de filtrado: tengo una lista y debo filtrarla por un atributo de los elementos.

Mi código se veía así:

my_list = [x for x in my_list if x.attribute == value]

Pero luego pensé, ¿no sería mejor escribirlo así?

my_list = filter(lambda x: x.attribute == value, my_list)

Es más legible, y si es necesario para el rendimiento, la lambda podría sacarse para obtener algo.

La pregunta es: ¿hay alguna advertencia al usar la segunda vía? Cualquier diferencia de rendimiento? ¿Me estoy perdiendo completamente el Camino Pitónico y debería hacerlo de otra manera (como usar itemgetter en lugar de lambda)?


606
2018-06-10 10:14


origen


Respuestas:


Es extraño cuánto varía la belleza para diferentes personas. La lista de comprensión me parece mucho más clara que filter+lambda, pero usa lo que encuentres más fácil. Sin embargo, deje de dar sus nombres de variables ya utilizados para complementos, eso es confuso.

Hay dos cosas que pueden ralentizar su uso de filter.

La primera es la sobrecarga de llamada de función: tan pronto como utilice una función de Python (ya sea creada por def o lambda) es probable que el filtro sea más lento que la comprensión de la lista. Es casi seguro que no es suficiente importar, y no debe pensar demasiado en el rendimiento hasta que haya cronometrado su código y lo haya encontrado como un cuello de botella, pero la diferencia estará allí.

La otra sobrecarga que podría aplicarse es que la lambda está siendo forzada a acceder a una variable de ámbito (value) Eso es más lento que acceder a una variable local y en Python 2.x la comprensión de la lista solo accede a las variables locales. Si está utilizando Python 3.x, la comprensión de la lista se ejecuta en una función separada, por lo que también estará accediendo value a través de un cierre y esta diferencia no se aplicará.

La otra opción a considerar es usar un generador en lugar de una lista de comprensión:

def filterbyvalue(seq, value):
   for el in seq:
       if el.attribute==value: yield el

Luego, en su código principal (que es donde realmente importa la legibilidad), ha reemplazado tanto la comprensión de la lista como el filtro con un nombre de función con sentido.


419
2018-06-10 10:52



Este es un problema un tanto religioso en Python. Aunque Guido consideró remover map, filter y reduce de Python 3, hubo una reacción suficiente que al final solo reduce se movió de incorporados a functools.reduce.

Personalmente, encuentro que la lista de comprensiones es más fácil de leer. Es más explícito lo que está sucediendo a partir de la expresión [i for i in list if i.attribute == value] como todo el comportamiento está en la superficie no dentro de la función de filtro.

No me preocuparía demasiado sobre la diferencia de rendimiento entre los dos enfoques, ya que es marginal. Realmente solo optimizaría esto si resultara ser el cuello de botella en su aplicación lo que es poco probable.

También desde el BDFL querido filter pasado del lenguaje, entonces eso automáticamente hace que las comprensiones de listas sean más Pythonic ;-)


183
2018-06-10 10:58



Dado que cualquier diferencia de velocidad será minúscula, el uso de filtros o la lista de comprensiones se reduce a una cuestión de gusto. En general, me inclino a usar las comprensiones (que parecen estar de acuerdo con la mayoría de las demás respuestas aquí), pero hay un caso en el que prefiero filter.

Un caso de uso muy frecuente es extraer los valores de un sujeto X repetible a un predicado P (x):

[x for x in X if P(x)]

pero a veces quieres aplicar primero alguna función a los valores:

[f(x) for x in X if P(f(x))]


Como ejemplo específico, considere

primes_cubed = [x*x*x for x in range(1000) if prime(x)]

Creo que esto se ve un poco mejor que usar filter. Pero ahora considera

prime_cubes = [x*x*x for x in range(1000) if prime(x*x*x)]

En este caso, queremos filter contra el valor post-calculado. Además del problema de calcular el cubo dos veces (imagine un cálculo más caro), está la cuestión de escribir la expresión dos veces, violando el SECO estético. En este caso, sería apto para usar

prime_cubes = filter(prime, [x*x*x for x in range(1000)])

50
2017-11-13 20:00



A pesar de que filter puede ser la "manera más rápida", la "vía Pythonic" sería no preocuparse por tales cosas a menos que el rendimiento sea absolutamente crítico (¡en cuyo caso no estaría usando Python!).


25
2018-06-10 10:22



Pensé que simplemente agregaría que en python 3, filter () es en realidad un objeto iterador, por lo que tendría que pasar su método de filtro call to list () para construir la lista filtrada. Entonces en Python 2:

lst_a = range(25) #arbitrary list
lst_b = [num for num in lst_a if num % 2 == 0]
lst_c = filter(lambda num: num % 2 == 0, lst_a)

las listas byc tienen los mismos valores, y se completaron casi al mismo tiempo que filter () era equivalente [x para x en y si z]. Sin embargo, en 3, este mismo código dejaría la lista c que contiene un objeto de filtro, no una lista filtrada. Para producir los mismos valores en 3:

lst_a = range(25) #arbitrary list
lst_b = [num for num in lst_a if num % 2 == 0]
lst_c = list(filter(lambda num: num %2 == 0, lst_a))

El problema es que list () toma un iterable como argumento, y crea una nueva lista a partir de ese argumento. El resultado es que usar el filtro de esta manera en python 3 toma hasta el doble que el método [x para x en y si z] porque tiene que iterar sobre la salida de filter () y la lista original.


13
2017-09-06 06:26



Una diferencia importante es que la comprensión de la lista devolverá una list mientras el filtro devuelve un filter, que no puedes manipular como un list (es decir: llamada len en él, que no funciona con el regreso de filter)

Mi propio autoaprendizaje me llevó a un problema similar.

Dicho esto, si hay una manera de tener el resultado list a partir de una filter, un poco como lo haría en .NET cuando lo hace lst.Where(i => i.something()).ToList(), Tengo curiosidad por saberlo.

EDITAR: Este es el caso de Python 3, no 2 (ver discusión en los comentarios).


9
2017-10-15 23:50



Encuentro que la segunda forma es más legible. Te dice exactamente cuál es la intención: filtrar la lista.
PD: no use 'list' como nombre de variable


8
2018-06-10 10:19