la comprensión de listas vs lambda + filtro


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 entonces 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 en el uso de la segunda manera? ¿Alguna diferencia de rendimiento? Me falta el Pitónico Camino™ completamente y debe hacerlo de otra manera (como el uso de itemgetter en lugar de la lambda)?

Author: ivanleoncz, 2010-06-10

14 answers

Es extraño cuánta belleza varía para diferentes personas. Encuentro la comprensión de la lista mucho más clara que filter+lambda, pero usa lo que te resulte más fácil. Sin embargo, deje de dar los nombres de sus variables ya utilizados para built-ins, eso es confuso.

Hay dos cosas que pueden ralentizar su uso de 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 el comprensión de la lista. Es casi seguro que no es suficiente para importar, y no debe pensar mucho en el rendimiento hasta que haya cronometrado su código y lo encontró como un cuello de botella, pero la diferencia estará ahí.

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 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 accederá a 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 comprensión de lista:

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

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 esperanza significativo.

 437
Author: Duncan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-06 13:22:44

Este es un tema un tanto religioso en Python. Aunque Guido considera eliminar map, filter y reduce de Python 3, hubo suficiente reacción que al final solo reduce se movió de built-ins a functools.reducir.

Personalmente encuentro las comprensiones de lista más fáciles de leer. Es más explícito lo que está sucediendo desde 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 sobre la diferencia de rendimiento entre los dos enfoques, ya que es marginal. Realmente solo optimizaría esto si demostrara ser el cuello de botella en su aplicación, lo cual es poco probable.

También desde el BDFL querido filter desaparecido del lenguaje entonces seguramente que automáticamente hace que las comprensiones de lista sean más pitónicas; -)

 189
Author: Tendayi Mawushe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-04 17:41:40

Dado que cualquier diferencia de velocidad está destinada a ser minúscula, si usar filtros o listas de comprensiones se reduce a una cuestión de gusto. En general, me inclino a usar 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 sujeto X iterable a un predicado P (x):

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

Pero a veces desea 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 filter. Pero ahora considere

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 de la cuestión de calcular el cubo dos veces (imagínese un cálculo más caro), está la cuestión de escribir la expresión dos veces, violando la SECA estética. En este caso yo sería apto para utilizar

prime_cubes = filter(prime, [x*x*x for x in range(1000)])
 53
Author: I. J. Kennedy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-27 08:05:49

Aunque filter puede ser la" forma más rápida", la" forma pitónica " sería no preocuparse por tales cosas a menos que el rendimiento sea absolutamente crítico (en cuyo caso no estaría utilizando Python!).

 25
Author: Umang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-10 10:22:36

Pensé en agregar que en python 3, filter() es en realidad un objeto iterador, por lo que tendría que pasar su llamada al método filter 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 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 conteniendo 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 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 filter de esta manera en python 3 toma hasta el doble de tiempo que el método [x para x en y si z] porque tienes que iterar sobre la salida de filter() así como la lista original.

 14
Author: Jim50,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-06 06:48:17

Una diferencia importante es que la comprensión de la lista devolverá un list mientras que el filtro devuelve un filter, que no se puede manipular como un list (es decir: call len en él, que no funciona con el retorno de filter).

Mi propio autoaprendizaje me llevó a un tema similar.

Es decir, si hay una manera de tener el resultante list de a filter, un poco como lo haría en .NET cuando do lst.Where(i => i.something()).ToList(), tengo curiosidad por saberlo.

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

 9
Author: Adeynack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-31 14:27:35

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

 8
Author: unbeli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-10 10:19:27

Filter es solo eso. Filtra los elementos de una lista. Puedes ver que la definición menciona lo mismo (en el enlace oficial de documentos que mencioné antes). Considerando que, la comprensión de lista es algo que produce una nueva lista después de actuar sobre algo en la lista anterior.(Tanto el filtro como la comprensión de la lista crean una nueva lista y no realizan la operación en lugar de la lista anterior. Una nueva lista aquí es algo así como una lista con, por ejemplo, un tipo de datos completamente nuevo. Me gusta convertir enteros a cadena ,etc)

En su ejemplo, es mejor usar filtro que comprensión de lista, según la definición. Sin embargo, si desea, por ejemplo other_attribute de los elementos de la lista, en su ejemplo se recuperará como una nueva lista, entonces puede usar comprensión de lista.

return [item.other_attribute for item in my_list if item.attribute==value]

Así es como realmente recuerdo sobre la comprensión de filtros y listas. Elimine algunas cosas dentro de una lista y mantenga los otros elementos intactos, use filtro. Utilice un poco de lógica por su cuenta en los elementos y cree una lista diluida adecuada para algún propósito, use comprensión de lista.

 7
Author: thiruvenkadam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-29 07:38:11

Generalmente filter es ligeramente más rápido si se usa una función incorporada.

Esperaría que la comprensión de la lista sea un poco más rápida en su caso

 5
Author: John La Rooy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-10 10:17:47

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:

# 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])]
 5
Author: rharder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-28 19:31:41

Me tomó algún tiempo para familiarizarse con el higher order functions filter y map. Así que me acostumbré a ellos y en realidad me gustó filter ya que era explícito que se filtra manteniendo lo que sea veraz y me he sentido genial que sabía algunos términos functional programming.

Luego leí este pasaje (Fluent Python Book): {[18]]}

Las funciones de mapa y filtro siguen integradas en Python 3, pero desde la introducción de comprensiones de listas y generador ex‐ pressions, no son tan importantes. Un listcomp o un genexp hace el trabajo de map y filtro combinado, pero es más legible.

Y ahora pienso, ¿por qué molestarse con el concepto de filter / map si puede lograrlo con expresiones ya ampliamente difundidas como comprensiones de listas. Además maps y filters son tipos de funciones. En este caso prefiero usar Anonymous functions lambdas.

Finalmente, solo por el bien de probarlo, he cronometrado ambos métodos (map y listComp) y no vi ninguna diferencia de velocidad relevante que justificara 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
 3
Author: user1767754,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-28 00:27:01

Además de la respuesta aceptada, hay un caso de esquina cuando debe usar filtro en lugar de una comprensión de lista. Si la lista no se puede lavar, no se puede procesar directamente con una comprensión de lista. Un ejemplo del mundo real es si utiliza pyodbc para leer los resultados de una base de datos. El fetchAll() resultado de cursor es una lista inutilizable. 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 comprensión de lista aquí obtendrá la error:

TypeError: unhashable type:'list'

 3
Author: C.W.praen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-13 19:55:39

Curiosamente en Python 3, veo que el filtro funciona más rápido que las comprensiones de listas.

Siempre pensé que la comprensión de la lista sería más eficaz. Algo como: [name for name in brand_names_db if name is not None] 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
 1
Author: Rod Senra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-10-03 19:13:25

Mi toma

def filter_list(list, key, value, limit=None):
    return [i for i in list if i[key] == value][:limit]
 -5
Author: tim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-21 22:54:53