¿Es posible agregar una cláusula where con comprensión de lista?

Considere la siguiente lista de comprensión

[ (x,f(x)) for x in iterable if f(x) ] 

Esto filtra la condición basada en una condición f y devuelve los pares de x,f(x) . El problema con este enfoque es que f(x) se calcula dos veces. Sería genial si pudiéramos escribir como

 [ (x,fx) for x in iterable if fx where fx = f(x) ] or [ (x,fx) for x in iterable if fx with f(x) as fx ] 

Pero en Python tenemos que escribir usando comprensiones anidadas para evitar la duplicación de llamadas a f (x) y hace que la comprensión parezca menos clara.

 [ (x,fx) for x,fx in ( (y,f(y) for y in iterable ) if fx ] 

¿Hay alguna otra manera de hacerlo más python y legible?


Actualizar

¡Próximamente en Python 3.8! ENERGÍA

 # Share a subexpression between a comprehension filter clause and its output filtered_data = [y for x in data if (y := f(x)) is not None] 

Busca tener semántica-let en las comprensiones de la lista de python, cuyo scope está disponible tanto para ___ for..in (map) como if ___ (filtro) parte de la comprensión, y cuyo scope depende de la … ..for ___ in...


Su solución, modificada: su (como usted admite ilegible) solución de [ (x,fx) for x,fx in ( (y,f(y) for y in iterable ) if fx ] es la forma más sencilla de escribir la optimización .

Idea principal: levantar x en la tupla (x, f (x)).

Algunos argumentarían que la forma más “pirónica” de hacer las cosas sería la original [(x,f(x)) for x in iterable if f(x)] y aceptar las ineficiencias.

Sin embargo, puede factorizar el ((y,fy) for y in iterable) en una función, si planea hacerlo mucho. Esto es malo porque si alguna vez desea tener acceso a más variables que x,fx (por ejemplo x,fx,ffx ), tendrá que volver a escribir todas las comprensiones de su lista. Por lo tanto, esta no es una gran solución a menos que sepa con seguridad que solo necesita x,fx y un plan para reutilizar este patrón.


Expresión del generador:

Idea principal: use una alternativa más complicada a las expresiones generadoras: una en la que Python le permitirá escribir varias líneas.

Puedes usar una expresión generadora, con la que Python juega bien con:

 def xfx(iterable): for x in iterable: fx = f(x) if fx: yield (x,fx) xfx(exampleIterable) 

Así es como lo haría personalmente.


Memorización / almacenamiento en caché:

Idea principal: también podría usar (¿abuso?) Efectos secundarios y hacer que f tenga un caché de memoria global, para que no repita las operaciones.

Esto puede tener un poco de sobrecarga y requiere una política de cuán grande debe ser el caché y cuándo debe ser recolectada como basura. Por lo tanto, esto solo debe usarse si tendría otros usos para memorizar f, o si f es muy costoso. Pero te dejaría escribir …

 [ (x,f(x)) for x in iterable if f(x) ] 

… como lo deseaba originalmente sin el impacto de rendimiento de hacer las operaciones caras en f dos veces, incluso si técnicamente lo llama dos veces. Puede agregar un decorador de @memoized a f : example (sin el tamaño máximo de caché). Esto funcionará siempre que x sea hashable (por ejemplo, un número, una tupla, un frozenset, etc.).


Valores ficticios:

Idea principal: capturar fx = f (x) en un cierre y modificar el comportamiento de la lista de comprensión.

 filterTrue( (lambda fx=f(x): (x,fx) if fx else None)() for x in iterable ) 

donde filterTrue (iterable) es filter (Ninguno, iterable). Tendría que modificar esto si su tipo de lista (una tupla de 2) fuera realmente capaz de ser None .

No hay ninguna instrucción where , pero se puede “emular” usando for :

 a=[0] def f(x): a[0] += 1 return 2*x print [ (x, y) for x in range(5) for y in [f(x)] if y != 2 ] print "The function was executed %s times" % a[0] 

Ejecución:

 $ python 2.py [(0, 0), (2, 4), (3, 6), (4, 8)] The function was executed 5 times 

Como puede ver, las funciones se ejecutan 5 veces, no 10 o 9.

Esto for construcción:

 for y in [f(x)] 

Imitar donde la cláusula.

Nada dice que debes usar las comprensiones. De hecho, la mayoría de las guías de estilo que he visto solicitan que las limites a construcciones simples, de todos modos.

Podrías usar una expresión generadora, en su lugar.

 def fun(iterable): for x in iterable: y = f(x) if y: yield x, y print list(fun(iterable)) 

Mapa y Zip?

 fnRes = map(f, iterable) [(x,fx) for x,fx in zip(iterable, fnRes) if fx)]