¿Cómo puedo filtrar eficientemente los valores calculados dentro de una lista de comprensión de Python?

StackOverflow https://stackoverflow.com/questions/130262

  •  02-07-2019
  •  | 
  •  

Pregunta

La sintaxis de comprensión de la lista de Python facilita el filtrado de valores dentro de una comprensión. Por ejemplo:

result = [x**2 for x in mylist if type(x) is int]

Devolverá una lista de los cuadrados de enteros en mylist. Sin embargo, ¿qué sucede si la prueba implica algún cálculo (costoso) y desea filtrar el resultado? Una opción es:

result = [expensive(x) for x in mylist if expensive(x)]

Esto dará como resultado una lista de no " false " valores caros (x), sin embargo se llama dos veces a caros () para cada x ¿Existe una sintaxis de comprensión que le permita realizar esta prueba mientras solo llama caro una vez por x?

¿Fue útil?

Solución

Si los cálculos ya están bien agrupados en funciones, ¿qué hay de usar filter y map ?

result = filter (None, map (expensive, mylist))

Puede usar itertools.imap si la lista es muy grande.

Otros consejos

Llegué con mi propia respuesta después de un minuto de pensamiento. Se puede hacer con comprensiones anidadas:

result = [y for y in (expensive(x) for x in mylist) if y]

Supongo que eso funciona, aunque creo que las comprensiones anidadas solo se pueden leer de forma marginal

La respuesta más obvia (y yo diría que la más legible) es no usar una comprensión de lista o una expresión generadora, sino un generador real:

def gen_expensive(mylist):
    for item in mylist:
        result = expensive(item)
        if result:
            yield result

Toma más espacio horizontal, pero es mucho más fácil ver lo que hace de un vistazo, y terminas sin repetirte.

result = [x for x in map(expensive,mylist) if x]

map () devolverá una lista de los valores de cada objeto en mylist pasado a costoso (). Luego, puede hacer una lista, comprenderlo y descartar valores innecesarios.

Esto es algo así como una comprensión anidada, pero debería ser más rápida (ya que el intérprete de Python puede optimizarlo con bastante facilidad).

Esto es exactamente lo que los generadores son adecuados para manejar:

result = (expensive(x) for x in mylist)
result = (do_something(x) for x in result if some_condition(x))
...
result = [x for x in result if x]  # finally, a list
  1. Esto hace que quede totalmente claro lo que está sucediendo en cada etapa de la tubería.
  2. Explícito sobre implícito
  3. Utiliza generadores en todas partes hasta el paso final, por lo que no hay grandes listas intermedias

cf: 'Trucos del generador para programadores del sistema' por David Beazley

Siempre podría memorizar la función expensive () para que llamar a la segunda vez es simplemente una búsqueda del valor computado de x .

Esta es solo una de las muchas implementaciones de memoize como un decorador . / p>

Podría memorizar caro (x) (y si llama caro (x) con frecuencia, probablemente debería hacerlo de alguna manera. Esta página ofrece una implementación de memoize para python:

http://code.activestate.com/recipes/52201/

Esto tiene el beneficio adicional de que costoso (x) puede ejecutarse menos que N veces, ya que cualquier entrada duplicada hará uso de la nota de la ejecución anterior.

Tenga en cuenta que esto supone que costoso (x) es una función verdadera y no depende del estado externo que pueda cambiar. Si caro (x) depende del estado externo, y puede detectar cuándo cambia ese estado, o sabe que no cambiará durante la comprensión de la lista, entonces puede restablecer las notas antes de la comprensión.

Tendré una preferencia por:

itertools.ifilter(bool, (expensive(x) for x in mylist))

Esto tiene la ventaja de:

También existe el antiguo uso de un bucle para para adjuntarlo a una lista:

result = []
for x in mylist:
    expense = expensive(x)
    if expense:
        result.append(expense)
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top