Me encontré con una necesidad básica de filtrado: tengo una lista y tengo que 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, se podría sacar la lambda para obtener algo.
La pregunta es: ¿hay alguna advertencia al usar la segunda forma? ¿Alguna diferencia de rendimiento? ¿Me estoy perdiendo el Pythonic Way ™ por completo y debería hacerlo de otra manera (como usar itemgetter en lugar de lambda)?
filter
era más legible. Cuando tiene una expresión simple que se puede usar como está en una listacomp, pero tiene que estar envuelta en una lambda (o construida de manera similar a partir departial
ooperator
funciones, etc.) para pasarfilter
, entonces es cuando ganan las listas.filter
es un objeto generador de filtro, no una lista.Respuestas:
Es extraño cuánto varía la belleza para diferentes personas. Encuentro la comprensión de la lista mucho más clara que
filter
+lambda
, pero use la que le resulte más fácil.Hay dos cosas que pueden retrasar su uso
filter
.El primero es la sobrecarga de la llamada de función: tan pronto como use una función de Python (ya sea creada por
def
olambda
) es probable que el filtro sea más lento que la comprensión de la lista. Es casi seguro que no sea suficiente, y no debe pensar mucho en el rendimiento hasta que haya cronometrado su código y haya encontrado que es un cuello de botella, pero la diferencia estará ahí.La otra sobrecarga que podría aplicarse es que la lambda se ve obligada 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 se accederá avalue
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:
Luego, en su código principal (que es donde la legibilidad realmente importa) ha reemplazado tanto la comprensión de la lista como el filtro con un nombre de función con significado significativo.
fuente
[]
a()
. Además, estoy de acuerdo en que el borrador de la lista es más hermoso.filter
que es más rápido usando una función de devolución de llamada de Python.Este es un tema algo religioso en Python. A pesar de que Guido consideró eliminarlo
map
,filter
yreduce
de Python 3 , hubo suficiente reacción violenta que al final soloreduce
se movió de las funciones integradas a functools.reduce .Personalmente, encuentro las comprensiones de listas más fáciles de leer. Es más explícito lo que está sucediendo en la expresión,
[i for i in list if i.attribute == value]
ya que todo el comportamiento está en la superficie, no dentro de la función de filtro.No me preocuparía demasiado 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 cual es poco probable.
Además, dado que el BDFL quería
filter
pasar del lenguaje, seguramente eso automáticamente hace que las comprensiones de listas sean más pitónicas ;-)fuente
Dado que cualquier diferencia de velocidad está destinada a ser minúscula, usar filtros o comprender las listas se reduce a una cuestión de gustos. En general, me inclino a usar las comprensiones (que parece estar de acuerdo con la mayoría de las otras respuestas aquí), pero hay un caso en el que prefiero
filter
.Un caso de uso muy frecuente es extraer los valores de algún X iterable sujeto a un predicado P (x):
pero a veces quieres aplicar alguna función a los valores primero:
Como ejemplo específico, considere
Creo que esto se ve un poco mejor que usar
filter
. Pero ahora consideraEn 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 costoso), está el problema de escribir la expresión dos veces, violando la estética DRY . En este caso, sería apto para usarfuente
[prime(i) for i in [x**3 for x in range(1000)]]
x*x*x
no puede ser un número primo, como lo ha sidox^2
yx
como factor, el ejemplo realmente no tiene sentido de manera matemática, pero tal vez aún sea útil. (Tal vez podríamos encontrar algo mejor, aunque?)prime_cubes = filter(prime, (x*x*x for x in range(1000)))
prime_cubes = [1]
ahorrar tanto memoria como ciclos de CPU ;-)[]
Aunque
filter
puede ser la "forma más rápida", la "forma pitónica" sería no preocuparse por esas cosas a menos que el rendimiento sea absolutamente crítico (¡en cuyo caso no estaría usando Python!).fuente
Pensé que solo agregaría que en Python 3, filter () es en realidad un objeto iterador, por lo que tendría que pasar su llamada al método de filtro a list () para construir la lista filtrada. Entonces en Python 2:
las listas byc tienen los mismos valores, y se completaron aproximadamente 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:
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 de tiempo que el método [x para x en y si z] porque debe iterar sobre la salida del filtro (), así como la lista original.
fuente
Una diferencia importante es que la comprensión de la lista devolverá un
list
tiempo, mientras que el filtro devuelve unfilter
, que no puede manipular como unlist
(es decir, invocarlolen
, que no funciona con el retorno defilter
).Mi propio autoaprendizaje me llevó a un problema similar.
Dicho esto, si hay una manera de obtener el resultado
list
de unfilter
, un poco como lo haría en .NET cuando lo hagalst.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).
fuente
a = [1, 2, 3, 4, 5, 6, 7, 8]
f = filter(lambda x: x % 2 == 0, a)
lc = [i for i in a if i % 2 == 0]
>>> type(f)
<class 'filter'>
>>> type(lc)
<class 'list'>
list()
en el resultado:list(filter(my_func, my_iterable))
. Y, por supuesto, podría reemplazarlist
conset
, otuple
, o cualquier otra cosa que requiera un iterable. Pero para cualquiera que no sean programadores funcionales, el caso es aún más fuerte para usar una comprensión de la lista en lugar defilter
una conversión más explícitalist
.Encuentro la segunda forma más legible. Te dice exactamente cuál es la intención: filtrar la lista.
PD: no use 'lista' como nombre de variable
fuente
generalmente
filter
es un poco más rápido si se usa una función integrada.Esperaría que la comprensión de la lista sea un poco más rápida en su caso
fuente
El filtro es solo eso. Filtra los elementos de una lista. Puede ver que la definición menciona lo mismo (en el enlace de documentos oficiales que mencioné antes). Mientras que la comprensión de la lista es algo que produce una nueva lista después de actuar sobre algo de la lista anterior. , digamos, un tipo de datos completamente nuevo. Como convertir enteros a cadenas, etc.)
En su ejemplo, es mejor usar filtro que comprensión de listas, según la definición. Sin embargo, si lo desea, diga other_attribute de los elementos de la lista, en su ejemplo se debe recuperar como una nueva lista, entonces puede usar la comprensión de la lista.
Así es como realmente recuerdo acerca de la comprensión de filtros y listas. Elimine algunas cosas dentro de una lista y mantenga los otros elementos intactos, use filtro. Use un poco de lógica por su cuenta en los elementos y cree una lista diluida adecuada para algún propósito, use la comprensión de la lista.
fuente
Aquí hay una pieza corta que uso cuando necesito filtrar algo después de la comprensión de la lista. Solo una combinación de filtro, lambda y listas (también conocida como la lealtad de un gato y la limpieza de un perro).
En este caso, estoy leyendo un archivo, eliminando líneas en blanco, líneas comentadas y cualquier cosa después de un comentario en una línea:
fuente
file_contents = list(filter(None, (s.partition('#')[0].strip() for s in lines)))
Además de la respuesta aceptada, hay un caso de esquina en el que debe usar filtro en lugar de una comprensión de lista. Si la lista no se puede compartir, no puede procesarla directamente con una comprensión de la lista. Un ejemplo del mundo real es si usas
pyodbc
para leer resultados de una base de datos. LosfetchAll()
resultados decursor
es una lista inquebrantable. En esta situación, para manipular directamente los resultados devueltos, se debe utilizar el filtro:Si utiliza la comprensión de la lista aquí, obtendrá el error:
fuente
>>> hash(list()) # TypeError: unhashable type: 'list'
segundo lugar, esto funciona bien:processed_data = [s for s in data_from_db if 'abc' in s.field1 or s.StartTime >= start_date_time]
Me tomó un tiempo familiarizarme con el
higher order functions
filter
ymap
. Así que me acostumbré a ellos y en realidad me gustó,filter
ya que era explícito que se filtra manteniendo lo que es verdadero y me sentí bien al saber algunosfunctional programming
términos.Luego leí este pasaje (Fluent Python Book):
Y ahora pienso, ¿por qué molestarse con el concepto de
filter
/map
si puede lograrlo con expresiones idiomáticas ya ampliamente difundidas como las comprensiones de listas? Ademásmaps
yfilters
son un tipo de funciones. En este caso prefiero usarAnonymous functions
lambdas.Finalmente, solo por el hecho de haberlo probado, he cronometrado ambos métodos (
map
ylistComp
) y no vi ninguna diferencia de velocidad relevante que justificara hacer argumentos al respecto.fuente
Curiosamente en Python 3, veo que el filtro funciona más rápido que las listas de comprensión.
Siempre pensé que las comprensiones de la lista serían más efectivas. Algo así como: [nombre para el nombre en brand_names_db si el nombre no es Ninguno] El código de bytes generado es un poco mejor.
Pero en realidad son más lentos:
fuente
if not None
en la comprensión de la lista, está definiendo una función lambda (observe laMAKE_FUNCTION
declaración). Segundo, los resultados son diferentes, ya que la versión de comprensión de la lista eliminará solo elNone
valor, mientras que la versión del filtro eliminará todos los valores "falsos". Dicho esto, todo el propósito de la microbenchmarking es inútil. ¡Esas son un millón de iteraciones, multiplicadas por mil artículos! La diferencia es insignificante .Mi toma
fuente
i
nunca se dijo que fuera undict
, y no hay necesidad de hacerlolimit
. Aparte de eso, ¿en qué se diferencia esto de lo que sugirió el OP y cómo responde a la pregunta?