Encuentre el primer elemento en una secuencia que coincida con un predicado

Quiero una forma idiomática de encontrar el primer elemento en una lista que coincida con un predicado.

El código actual es bastante feo:

[x for x in seq if predicate(x)][0] 

He pensado en cambiarlo a:

 from itertools import dropwhile dropwhile(lambda x: not predicate(x), seq).next() 

Pero debe haber algo más elegante … Y sería bueno si devuelve un valor None lugar de generar una excepción si no se encuentra una coincidencia.

Sé que podría definir una función como:

 def get_first(predicate, seq): for i in seq: if predicate(i): return i return None 

Pero es bastante insípido comenzar a llenar el código con funciones de utilidad como esta (y las personas probablemente no se darán cuenta de que ya están allí, por lo que tienden a repetirse con el tiempo) si hay integradas que ya proporcionan lo mismo.

next(x for x in seq if predicate(x))

Plantea StopIteration si no hay ninguno.

next(ifilter(predicate, seq), None)

devuelve None si no existe tal elemento.

Podría usar una expresión de generador con un valor predeterminado y luego a next :

 next((x for x in seq if predicate(x)), None) 

Aunque para este one-liner necesitas estar usando Python> = 2.6.

Este artículo bastante popular analiza más a fondo este problema: ¿La función de búsqueda en lista más limpia de Python? .

No creo que haya nada malo con las soluciones que propusiste en tu pregunta.

En mi propio código, lo implementaría así:

 (x for x in seq if predicate(x)).next() 

La syntax con () crea un generador, que es más eficiente que generar toda la lista a la vez con [] .

La respuesta de JF Sebastian es muy elegante, pero requiere Python 2.6 como señala Fortran.

Para la versión Python <2.6, esto es lo mejor que puedo encontrar:

 from itertools import repeat,ifilter,chain chain(ifilter(predicate,seq),repeat(None)).next() 

Alternativamente, si necesitaba una lista más adelante (la lista maneja el StopIteration), o si necesitaba más que la primera pero aún no todas, puede hacerlo con islice:

 from itertools import islice,ifilter list(islice(ifilter(predicate,seq),1)) 

ACTUALIZACIÓN: Aunque personalmente estoy usando una función predefinida llamada first () que detecta una StopIteration y no devuelve ninguna, aquí hay una posible mejora con respecto al ejemplo anterior: evite usar filter / ifilter:

 from itertools import islice,chain chain((x for x in seq if predicate(x)),repeat(None)).next()