Evitar llamadas de función repetidas en la syntax de comprensión de lista

Tengo una lista de comprensión que se aproxima a:

[f(x) for x in l if f(x)] 

Donde l es una lista y f (x) es una función costosa que devuelve una lista.

Quiero evitar evaluar f (x) dos veces por cada ocurrencia no vacía de f (x). ¿Hay alguna manera de guardar su salida dentro de la lista de comprensión?

Podría eliminar la condición final, generar la lista completa y luego podarla, pero eso parece un desperdicio.

Una solución (la mejor si tiene un valor repetido de x) sería memorizar la función f, es decir, crear una función de envoltorio que guarde el argumento por el cual se llama a la función y guardarlo, que devolverlo si se solicita el mismo valor .

Una implementación muy simple es la siguiente:

 storage = {} def memoized(value): if value not in storage: storage[value] = f(value) return storage[value] [memoized(x) for x in l if memoized(x)] 

y luego usar esta función en la lista de comprensión. Este enfoque es válido bajo dos condiciones, una teórica y otra práctica. La primera es que la función f debe ser determinista, es decir, devuelve los mismos resultados con la misma entrada, y la otra es que el objeto x se puede usar como una clave de diccionario. Si la primera no es válida, debe volver a calcular cada definición de tiempo, mientras que si la segunda falla, es posible utilizar algunos métodos ligeramente más robustos.

Puedes encontrar una gran cantidad de implementaciones de memoria en la red, y creo que las nuevas versiones de python también tienen algo incluido.

En una nota al margen, nunca use la pequeña L como nombre variable, es un mal hábito ya que puede confundirse con una i o un 1 en algunos terminales.

EDITAR:

como se comentó, una posible solución que utiliza la comprensión de los generadores (para evitar crear temporarios duplicados inútiles) sería esta expresión:

 [g(x, fx) for x, fx in ((x,f(x)) for x in l) if fx] 

Debe ponderar su elección dado el costo computacional de f, el número de duplicaciones en la lista original y la memoria a su disposición. La memorización hace un intercambio de velocidad espacial, lo que significa que mantiene un registro de cada resultado guardándolo, por lo que si tiene listas enormes puede ser costoso en el frente de la ocupación de la memoria.

 [y for y in (f(x) for x in l) if y] 

Lo haré

Usted debe utilizar un decorador memoize. Aquí hay un enlace interesante.


Usando la memoria del enlace y su ‘código’:

 def memoize(f): """ Memoization decorator for functions taking one or more arguments. """ class memodict(dict): def __init__(self, f): self.f = f def __call__(self, *args): return self[args] def __missing__(self, key): ret = self[key] = self.f(*key) return ret return memodict(f) @memoize def f(x): # your code [f(x) for x in l if f(x)] 
 [y for y in [f(x) for x in l] if y] 

Para su problema actualizado, esto podría ser útil:

 [g(x,y) for x in l for y in [f(x)] if y] 

No No hay una forma ( limpia ) de hacer esto. No hay nada malo con un bucle anticuado:

 output = [] for x in l: result = f(x) if result: output.append(result) 

Si le resulta difícil de leer, siempre puede envolverlo en una función.

Como se ha mostrado en las respuestas anteriores, puede utilizar una doble comprensión o la memorización. Para problemas de tamaño razonable es cuestión de gustos (y estoy de acuerdo en que la memoria parece más clara, ya que oculta la optimización). Pero si está examinando una lista muy grande, hay una gran diferencia: la memorización almacenará todos los valores que haya calculado y puede agotar su memoria rápidamente. Una doble comprensión con un generador (paréntesis redondas, no corchetes) solo almacena lo que desea conservar.

Para llegar a su problema real:

 [g(x, f(x)) for x in series if f(x)] 

Para calcular el valor final necesitas tanto x como f(x) . No hay problema, pásalos a los dos así:

 [g(x, y) for (x, y) in ( (x, f(x)) for x in series ) if y ] 

Nuevamente: esto debería usar un generador (paréntesis redondos), no una lista de comprensión (corchetes). De lo contrario , construirá la lista completa antes de comenzar a filtrar los resultados. Esta es la versión de la lista de comprensión:

 [g(x, y) for (x, y) in [ (x, f(x)) for x in series ] if y ] # DO NOT USE THIS 

Puedes usar la memoización . Es una técnica que se usa para evitar hacer el mismo cálculo dos veces al guardar en alguna parte el resultado para cada valor calculado. Vi que ya existe una respuesta que utiliza la memoria, pero me gustaría proponer una implementación genérica, utilizando decoradores de python:

 def memoize(func): def wrapper(*args): if args in wrapper.d: return wrapper.d[args] ret_val = func(*args) wrapper.d[args] = ret_val return ret_val wrapper.d = {} return wrapper @memoize def f(x): ... 

Ahora f es una versión memorizada de sí mismo. Con esta implementación puedes memorizar cualquier función usando el decorador @memoize .

Ha habido muchas respuestas con respecto a la memoización. La biblioteca estándar de Python 3 ahora tiene un lru_cache , que es un último caché recientemente utilizado . Así que puedes:

 from functools import lru_cache @lru_cache() def f(x): # function body here 

De esta manera su función solo será llamada una vez. También puede especificar el tamaño de lru_cache , de forma predeterminada, es 128. El problema con los decoradores memoize que se muestran arriba es que el tamaño de las listas puede crecer fuera de control.

Usa el map() !!

 comp = [x for x in map(f, l) if x] 

f es la función f(X) , l es la lista

map() devolverá el resultado de f(x) para cada x en la lista.

Aquí está mi solución:

 filter(None, [f(x) for x in l]) 

¿Qué hay de definir:

 def truths(L): """Return the elements of L that test true""" return [x for x in L if x] 

Así que, por ejemplo,

 > [wife.children for wife in henry8.wives] [[Mary1], [Elizabeth1], [Edward6], [], [], []] > truths(wife.children for wife in henry8.wives) [[Mary1], [Elizabeth1], [Edward6]]