Найти первый элемент в последовательности, которая соответствует предикату

Глупый вопрос: мне нужен идиоматический способ найти первый элемент в списке, который соответствует предикату.

Текущий код довольно уродливый:

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

Я подумал об изменении его:

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

Но должно быть что-то более элегантное... И было бы неплохо, если бы оно вернуло значение None, а не увеличивало исключение, если совпадение не найдено.

Я знаю, что могу просто определить такую ​​функцию, как:

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

Но совершенно безвкусно начать заполнять код такими полезными функциями (и люди, вероятно, не заметят, что они уже есть, поэтому они, как правило, повторяются с течением времени), если есть встроенные вставки, которые уже предоставляют одинаковые.

Ответ 1

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

Он поднимает StopIteration, если его нет.

next(ifilter(predicate, seq), None)

возвращает None, если такого элемента нет.

Ответ 2

Вы можете использовать выражение генератора со значением по умолчанию, а затем next it:

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

Хотя для этого однострочного интерфейса вам нужно использовать Python >= 2.6.

В этой довольно популярной статье мы обсудим эту проблему: Чистая функция find-in-list Python?.

Ответ 3

Я не думаю, что что-то не так с решениями, которые вы предложили в своем вопросе.

В моем собственном коде я бы реализовал его так:

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

Синтаксис с () создает генератор, который более эффективен, чем создание всего списка сразу с помощью [].

Ответ 4

J.F. Ответ Себастьяна наиболее изящный, но требует python 2.6, как указано fortran.

Для версии Python < 2.6, здесь лучшее, что я могу придумать:

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

Альтернативно, если вам нужен список позже (список обрабатывает StopIteration), или вам нужно больше, чем только первое, но все же не все, вы можете сделать это с помощью islice:

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

UPDATE: Хотя я лично использую предопределенную функцию first(), которая улавливает StopIteration и возвращает None, здесь возможно улучшение по сравнению с приведенным выше примером: не используйте filter/ifilter:

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