为什么Python中没有first(iterable)内置函数?

86 投票
6 回答
48485 浏览
提问于 2025-04-15 12:39

我在想,为什么Python的内置函数里没有一个叫first(iterable)的东西呢?它有点像any(iterable)all(iterable),虽然可能在某个标准库模块里有,但我在itertools里没看到。first这个函数可以快速返回第一个符合条件的结果,这样就可以避免不必要的操作,尤其是当操作数量可能是无限的时候;也就是说:

def identity(item):
    return item

def first(iterable, predicate=identity):
    for item in iterable:
        if predicate(item):
            return item
    raise ValueError('No satisfactory value found')

这样你就可以写出类似这样的代码:

denominators = (2, 3, 4, 5)
lcd = first(i for i in itertools.count(1)
    if all(i % denominators == 0 for denominator in denominators))

很明显,如果生成器没有结束,你就不能用list(generator)[0]来获取第一个元素。

或者如果你有一堆正则表达式需要匹配(当它们都有相同的groupdict接口时,这很有用):

match = first(regex.match(big_text) for regex in regexes)

通过避免使用list(generator)[0],你可以节省很多不必要的处理时间,并且在找到匹配时可以快速返回结果。

6 个回答

12

我最近问了一个类似的问题(现在被标记为这个问题的重复)。我关心的是我希望只用内置的功能来解决找到生成器中第一个真实值的问题。于是我自己想出了这个解决方案:

x = next((v for v in (f(x) for x in a) if v), False)

比如说,如果要找第一个正则表达式的匹配(不是第一个匹配的模式!),可以这样做:

patterns = [ r'\d+', r'\s+', r'\w+', r'.*' ]
text = 'abc'
firstMatch = next(
  (match for match in
    (re.match(pattern, text) for pattern in patterns)
   if match),
  False)

这个方法不会让条件判断被计算两次(如果只是返回模式的话就得这样),而且也没有使用像在列表推导式中那样的技巧。

不过,它有两个嵌套的生成器,而逻辑上其实只需要一个。所以如果能有一个更好的解决方案就更好了。

20

有一个叫做“first”的Pypi包,它可以做到这一点:

>>> from first import first
>>> first([0, None, False, [], (), 42])
42

比如,如果你想用它来返回第一个奇数,可以这样使用:

>> first([2, 14, 7, 41, 53], key=lambda x: x % 2 == 1)
7

如果你只是想从迭代器中返回第一个元素,不管它是否符合条件,可以这样做:

>>> first([0, None, False, [], (), 42], key=lambda x: True)
0

这个包非常小:它只包含这个函数,没有其他依赖,而且在Python 2和3上都能运行。它只有一个文件,所以你甚至不需要安装就可以使用。

实际上,这里几乎是整个源代码(来自版本2.0.1,由Hynek Schlawack发布,采用MIT许可证):

def first(iterable, default=None, key=None):
    if key is None:
        for el in iterable:
            if el:
                return el
    else:
        for el in iterable:
            if key(el):
                return el
    return default
59

Python 2中,如果你有一个迭代器,你可以直接调用它的next方法。就像这样:

>>> (5*x for x in xrange(2,4)).next()
10

Python 3中,你可以使用一个叫做next的内置函数来处理迭代器:

>>> next(5*x for x in range(2,4))
10

撰写回答