python - comprehension - lista de comprensión vs lambda+filtro
python filter example (14)
Resulta que tengo 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 entonces pensé, ¿no sería mejor escribirlo así?
my_list = filter(lambda x: x.attribute == value, my_list)
Es más legible, y si fuera necesario para el rendimiento, la lambda podría sacarse 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 debo hacerlo de otra manera (como usar itemgetter en lugar de la lambda)?
Además de la respuesta aceptada, hay un caso de esquina cuando debe usar el filtro en lugar de una lista de comprensión. Si la lista es inestable, no puede procesarla directamente con una lista de comprensión. Un ejemplo del mundo real es si utiliza pyodbc
para leer resultados de una base de datos. Los resultados de fetchAll()
del cursor
son una lista inasible. En esta situación, para manipular directamente los resultados devueltos, se debe usar el filtro:
cursor.execute("SELECT * FROM TABLE1;")
data_from_db = cursor.fetchall()
processed_data = filter(lambda s: ''abc'' in s.field1 or s.StartTime >= start_date_time, data_from_db)
Si utiliza la lista de comprensión aquí obtendrá el error:
TypeError: tipo descargable: ''lista''
Aquí hay una breve pieza 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, borrando líneas en blanco, líneas comentadas y cualquier cosa después de un comentario en una línea:
# Throw out blank lines and comments
with open(''file.txt'', ''r'') as lines:
# From the inside out:
# [s.partition(''#'')[0].strip() for s in lines]... Throws out comments
# filter(lambda x: x!= '''', [s.part... Filters out blank lines
# y for y in filter... Converts filter object to list
file_contents = [y for y in filter(lambda x: x != '''', [s.partition(''#'')[0].strip() for s in lines])]
Aunque el filter
puede ser la "forma más rápida", la "forma Pythonic" sería no preocuparse por tales cosas a menos que el rendimiento sea absolutamente crítico (¡en cuyo caso usted no estaría usando Python!).
Curiosamente en Python 3, veo que el filtro funciona más rápido que las listas de comprensión.
Siempre pensé que las listas de comprensión serían más eficaces. Algo como: [nombre para el nombre en brand_names_db si el nombre no es Ninguno] El bytecode generado es un poco mejor.
>>> def f1(seq):
... return list(filter(None, seq))
>>> def f2(seq):
... return [i for i in seq if i is not None]
>>> disassemble(f1.__code__)
2 0 LOAD_GLOBAL 0 (list)
2 LOAD_GLOBAL 1 (filter)
4 LOAD_CONST 0 (None)
6 LOAD_FAST 0 (seq)
8 CALL_FUNCTION 2
10 CALL_FUNCTION 1
12 RETURN_VALUE
>>> disassemble(f2.__code__)
2 0 LOAD_CONST 1 (<code object <listcomp> at 0x10cfcaa50, file "<stdin>", line 2>)
2 LOAD_CONST 2 (''f2.<locals>.<listcomp>'')
4 MAKE_FUNCTION 0
6 LOAD_FAST 0 (seq)
8 GET_ITER
10 CALL_FUNCTION 1
12 RETURN_VALUE
Pero en realidad son más lentos:
>>> timeit(stmt="f1(range(1000))", setup="from __main__ import f1,f2")
21.177661532000116
>>> timeit(stmt="f2(range(1000))", setup="from __main__ import f1,f2")
42.233950221000214
Debido a que cualquier diferencia de velocidad será minúscula, el hecho de usar filtros o listas de comprensión se reduce a una cuestión de gustos. En general, me inclino a usar las comprensiones (lo que parece estar de acuerdo con la mayoría de las otras respuestas aquí), pero hay un caso en el que prefiero el filter
.
Un caso de uso muy frecuente es extraer los valores de algunos X iterables sujetos a un predicado P (x):
[x for x in X if P(x)]
pero a veces quieres aplicar alguna función a los valores primero:
[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 el 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-computado. Además del problema de calcular el cubo dos veces (imagine un cálculo más costoso), existe el problema de escribir la expresión dos veces, violando la estética DRY . En este caso estaría dispuesto a usar
prime_cubes = filter(prime, [x*x*x for x in range(1000)])
Encuentro la segunda forma más legible. Le dice exactamente cuál es la intención: filtre la lista.
PS: no use ''lista'' como nombre de variable
Es extraño cómo varía la belleza para diferentes personas. Me parece que la comprensión de la lista es mucho más clara que filter
+ lambda
, pero uso lo que encuentre más fácil. Sin embargo, deje de dar nombres a sus variables que ya se usaron para los elementos integrados, eso es confuso.
Hay dos cosas que pueden ralentizar el uso del filter
.
La primera es la sobrecarga de llamada a la función: tan pronto como use una función Python (ya sea creada por def
o lambda
), es probable que el filtro sea más lento que la lista de comprensión. Es casi seguro que no es suficiente para importar, y no debería pensar mucho en el rendimiento hasta que haya cronometrado su código y haya encontrado 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 ( value
) con alcance. Eso es más lento que acceder a una variable local y en Python 2.x la comprensión de la lista solo tiene acceso 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 tendrá acceso al value
mediante 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 la comprensión y el filtro de la lista con un nombre de función con la esperanza de que sea significativo.
Este es un tema un tanto religioso en Python. A pesar de que Guido consideró eliminar el map
, el filter
y la reduce
de Python 3 , hubo una reacción suficiente que al final solo se reduce
de incorporados a functools.reduce .
Personalmente me parece más fácil leer listas de comprensión. Es más explícito lo que sucede a partir de la expresión [i for i in list if i.attribute == value]
ya que todo el comportamiento está en la superficie y 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 que el filter
desapareciera del idioma, seguramente eso hace que las comprensiones de las listas sean más Pythonic ;-)
Generalmente, el filter
es un poco más rápido si se usa una función incorporada.
Espero que la comprensión de la lista sea un poco más rápida en su caso
Me tomó un tiempo familiarizarme con el filter
y map
higher order functions
. Así que me acostumbré a ellos y realmente me gustó el filter
ya que era explícito que filtraba manteniendo lo que fuera verdad y me sentía bien porque sabía algunos términos de functional programming
.
Luego leí este pasaje (Fluent Python Book):
Las funciones de mapa y filtro todavía están integradas en Python 3, pero desde la introducción de las comprensiones de listas y las expresiones generadoras, no son tan importantes. Un listcomp o un genexp hace el trabajo de mapeo y filtro combinados, pero es más legible.
Y ahora pienso, ¿por qué molestarse con el concepto de filter
/ map
si puede lograrlo con idiomas ya ampliamente difundidos, como las listas de comprensión? Además los maps
y filters
son una especie de funciones. En este caso prefiero usar las Anonymous functions
lambdas.
Finalmente, solo por el hecho de haberlo probado, he cronometrado ambos métodos ( map
y listComp
) y no vi ninguna diferencia de velocidad relevante que justifique hacer argumentos al respecto.
from timeit import Timer
timeMap = Timer(lambda: list(map(lambda x: x*x, range(10**7))))
print(timeMap.timeit(number=100))
timeListComp = Timer(lambda:[(lambda x: x*x) for x in range(10**7)])
print(timeListComp.timeit(number=100))
#Map: 166.95695265199174
#List Comprehension 177.97208347299602
Mi toma
def filter_list(list, key, value, limit=None):
return [i for i in list if i[key] == value][:limit]
Pensé que solo agregaría que en python 3, filter () es en realidad un objeto iterador, así que tendrías que pasar la llamada del método de filtro a list () para construir la lista filtrada. Así que 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 b y c 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 de tiempo que el método [x para x en y if z] porque tiene que iterar sobre la salida del filtro () así como la lista original.
Una diferencia importante es que la comprensión de la lista devolverá una list
mientras que el filtro devuelve un filter
, que no puede manipular como una list
(es decir, len
llamada en ella, que no funciona con la devolución del filter
).
Mi propio autoaprendizaje me llevó a un problema similar.
Dicho esto, si hay una manera de obtener la list
resultante de un filter
, un poco como lo haría en .NET cuando hace lst.Where(i => i.something()).ToList()
, estoy Curioso por saberlo.
EDITAR: Este es el caso de Python 3, no 2 (ver discusión en los comentarios).
Filter 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é anteriormente). Considerando que, la comprensión de la lista es algo que produce una nueva lista después de actuar sobre algo en la lista anterior. (La comprensión de la lista y del filtro crea una nueva lista y no realiza la operación en lugar de la lista anterior. Una nueva lista aquí es algo así como una lista con , digamos, un tipo de datos completamente nuevo. Como convertir números enteros a cadenas, etc.)
En su ejemplo, es mejor usar filtro que comprensión de lista, según la definición. Sin embargo, si lo desea, diga other_attribute de los elementos de la lista, en su ejemplo se recuperará como una nueva lista, luego puede usar la comprensión de la lista.
return [item.other_attribute for item in my_list if item.attribute==value]
Así es como realmente recuerdo acerca de la comprensión de listas y filtros. Elimine algunas cosas dentro de una lista y mantenga los otros elementos intactos, use el 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.