我想要一种惯用的方法来查找匹配谓词的列表中的第一个元素。

当前的代码相当难看:

[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

但是,如果已经内置了提供相同功能的内置功能,那么开始用这样的实用函数填充代码是相当乏味的(人们可能不会注意到它们已经存在,所以它们往往会随着时间的推移而重复出现)。


查找序列seq中与谓词匹配的第一个元素:

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

或者仅仅是:

Python 2:

next(itertools.ifilter(predicate, seq))

Python 3:

next(filter(predicate, seq))

如果谓词不匹配任何元素,这些语句将引发StopIteration异常。


如果没有这样的元素则返回None:

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

Or:

next(filter(predicate, seq), None)

你可以使用一个带有默认值的生成器表达式,然后下一个:

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

不过对于这一行代码,您需要使用Python >= 2.6。

这篇相当流行的文章进一步讨论了这个问题:最干净的Python列表查找函数?


我不认为你提出的两个方案有什么问题。

在我自己的代码中,我会像这样实现它:

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

with()语法创建了一个生成器,这比使用[]一次生成所有列表更有效。


J.F. Sebastian的回答是最优雅的,但正如fortran指出的那样,需要python 2.6。

对于Python版本< 2.6,这里是我能想到的最好的:

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

或者,如果你以后需要一个列表(list处理StopIteration),或者你需要的不仅仅是第一个,但仍然不是全部,你可以用islice来做:

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

更新: 虽然我个人使用一个被称为first()的预定义函数来捕获StopIteration并返回None,这里有一个可能的改进:避免使用过滤器/过滤器:

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