una - función range de python
¿Cómo puedo filtrar de manera eficiente los valores computados dentro de una lista de Python? (9)
La respuesta más obvia (y sería más legible) es no usar una lista de comprensión o expresión de generador, sino un generador real:
def gen_expensive(mylist):
for item in mylist:
result = expensive(item)
if result:
yield result
Se necesita más espacio horizontal, pero es mucho más fácil ver lo que hace de un vistazo, y terminas no repitiéndote.
La sintaxis de comprensión de la lista de Python hace que sea fácil filtrar los 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é pasa 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 valores caros (x) no "falsos", no obstante lo caro () se llama dos veces para cada x. ¿Hay una sintaxis de comprensión que le permita hacer esta prueba mientras llama solo una vez por x?
Puede memorizar caro (x) (y si llama caro (x) con frecuencia, probablemente debería memorizarlo de cualquier forma. 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 de N veces, ya que cualquier entrada duplicada hará uso de la nota de la ejecución anterior.
Tenga en cuenta que esto supone que costosa (x) es una verdadera función, y no depende del estado externo que pueda cambiar. Si el costo (x) depende del estado externo, y puede detectar cuándo cambia ese estado, o si sabe que no cambiará durante la comprensión de la lista, puede restablecer los memos antes de la comprensión.
Si los cálculos ya están bien agrupados en funciones, ¿qué hay de usar el filter
y el map
?
result = filter (None, map (expensive, mylist))
Puede usar itertools.imap
si la lista es muy grande.
Siempre puede memorizar la función expensive()
para que llamarla por segunda vez sea simplemente una búsqueda del valor computado de x
.
Esta es solo una de las muchas implementaciones de memoize como decorador .
Terminé con mi propia respuesta después de un minuto de reflexión. Se puede hacer con comprensiones anidadas:
result = [y for y in (expensive(x) for x in mylist) if y]
Supongo que eso funciona, aunque encuentro que las comprensiones anidadas son solo ligeramente legibles
result = [x for x in map(expensive,mylist) if x]
map () devolverá una lista de los valores de cada objeto en mylist pasado a caro (). Entonces puede listar-comprender eso, y descartar valores innecesarios.
Esto es algo así como una comprensión anidada, pero debería ser más rápido (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
- Esto deja totalmente en claro lo que está sucediendo durante cada etapa de la tubería.
- Explícito sobre implícito
- Utiliza generadores en todas partes hasta el último paso, por lo que no hay grandes listas intermedias
cf: ''Trucos del generador para programadores de sistemas'' por David Beazley
También está el antiguo uso simple de un bucle for
para agregar a una lista:
result = []
for x in mylist:
expense = expensive(x)
if expense:
result.append(expense)
Tendré preferencia por:
itertools.ifilter(bool, (expensive(x) for x in mylist))
Esto tiene la ventaja de:
- evite None como función (se eliminará en Python 3): http://bugs.python.org/issue2186
- usar solo iteradores.