ylab color change python list-comprehension code-readability

color - Comprensión de la lista de Python: quiere evitar una evaluación repetida



python plotly axis (11)

¿Qué hay de definir:

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

Para que, por ejemplo

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

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 toda la lista y luego podarla, pero eso parece un desperdicio.

Editar :

Se han sugerido dos enfoques básicos:

Una comprensión de generador interno:

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

o memorización.

Creo que la comprensión interna del generador es elegante para el problema como se dijo. De hecho, simplifiqué la pregunta para dejar en claro, realmente quiero:

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

Para esta situación más complicada, creo que la memoria produce un resultado final más limpio.


Aquí está mi solución:

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


Como se mostró en las respuestas anteriores, puede usar una comprensión doble o utilizar la memorización. Para problemas de tamaño razonable es una cuestión de gusto (y estoy de acuerdo en que la memorización se ve más limpia, ya que oculta la optimización). Pero si está examinando una lista muy grande, existe una gran diferencia: la memorización almacenará todos y cada uno de los valores que ha calculado y puede hacer que su memoria se agote rápidamente. Una comprensión doble con un generador (parens redondos, no corchetes) solo almacena lo que desea conservar.

Para llegar a tu problema real:

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

Para calcular el valor final necesita tanto x como f(x) . No hay problema, páselos a ambos de esta manera:

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

De nuevo: esto debería ser usando un generador (parens redondos), no una lista de comprensión (corchetes). De lo contrario , compilará toda la lista antes de comenzar a filtrar los resultados. Esta es la lista de la versión de comprensión:

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


Deberías usar un decorador memoize. Aquí hay un link interesante.

Usando memoization desde el 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)]


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

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

De esta forma, su función solo se llamará una vez. También puede especificar el tamaño de lru_cache , por defecto es 128. El problema con los decoradores de memoise mostrados arriba es que el tamaño de las listas puede crecer fuera de control.


Nop. No hay una forma ( limpia ) de hacer esto. No hay nada malo con un bucle a la antigua:

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.


Puedes usar memoization . 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 hay una respuesta que utiliza la memoria, pero me gustaría proponer una implementación genérica, utilizando decoradores de pitón:

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 memorada de sí mismo. Con esta implementación puede memorizar cualquier función usando el decorador @memoize .


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 envoltura que guarda el argumento por el que se llama a la función y guardarla, que devolverla si se le pide el mismo valor .

una implementación realmente 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 use 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 claves de diccionario. Si el primero no es válido, debe volver a calcular f cada definición de tiempo, mientras que si el segundo falla, es posible utilizar enfoques un poco más robustos.

Puede encontrar mucha implementación de memoialización en la red, y creo que las nuevas versiones de python también tienen algo incluido en ellas.

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

EDITAR:

como se comentó, una posible solución usando la comprensión de generadores (para evitar la creación de temporales 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 una compensación de velocidad espacial, lo que significa que mantiene un registro de cada resultado, por lo que si tiene listas enormes, puede resultar costoso en el frente de ocupación de la memoria.


Usar 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.


[y for y in (f(x) for x in l) if y] lo hará


[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]