从可迭代对象中获取第一个符合条件的项

588 投票
19 回答
423330 浏览
提问于 2025-04-15 19:54

我想从一个列表中获取第一个符合特定条件的项目。重要的是,这个方法不要处理整个列表,因为列表可能会很大。例如,下面这个函数就可以满足这个需求:

def first(the_iterable, condition = lambda x: True):
    for i in the_iterable:
        if condition(i):
            return i

这个函数可以这样使用:

>>> first(range(10))
0
>>> first(range(10), lambda i: i > 3)
4

不过,我想不出一个好的内置方法或者一行代码来实现这个功能。如果不必要的话,我不想到处复制这个函数。有没有什么内置的方法可以直接获取第一个符合条件的项目呢?

19 个回答

47

作为一个可重用的、文档齐全且经过测试的函数

def first(iterable, condition = lambda x: True):
    """
    Returns the first item in the `iterable` that
    satisfies the `condition`.

    If the condition is not given, returns the first item of
    the iterable.

    Raises `StopIteration` if no item satysfing the condition is found.

    >>> first( (1,2,3), condition=lambda x: x % 2 == 0)
    2
    >>> first(range(3, 100))
    3
    >>> first( () )
    Traceback (most recent call last):
    ...
    StopIteration
    """

    return next(x for x in iterable if condition(x))

带有默认参数的版本

@zorf 提出了一个版本的这个函数,允许你在可迭代对象为空或者没有符合条件的项目时,返回一个预定义的值:

def first(iterable, default = None, condition = lambda x: True):
    """
    Returns the first item in the `iterable` that
    satisfies the `condition`.

    If the condition is not given, returns the first item of
    the iterable.

    If the `default` argument is given and the iterable is empty,
    or if it has no items matching the condition, the `default` argument
    is returned if it matches the condition.

    The `default` argument being None is the same as it not being given.

    Raises `StopIteration` if no item satisfying the condition is found
    and default is not given or doesn't satisfy the condition.

    >>> first( (1,2,3), condition=lambda x: x % 2 == 0)
    2
    >>> first(range(3, 100))
    3
    >>> first( () )
    Traceback (most recent call last):
    ...
    StopIteration
    >>> first([], default=1)
    1
    >>> first([], default=1, condition=lambda x: x % 2 == 0)
    Traceback (most recent call last):
    ...
    StopIteration
    >>> first([1,3,5], default=1, condition=lambda x: x % 2 == 0)
    Traceback (most recent call last):
    ...
    StopIteration
    """

    try:
        return next(x for x in iterable if condition(x))
    except StopIteration:
        if default is not None and condition(default):
            return default
        else:
            raise
70

真是烦人的异常!

我很喜欢Alex Martelli的回答。不过,由于next()在没有更多项目时会抛出一个StopIteration异常,所以我会使用下面的代码片段来避免这个异常:

a = []
item = next((x for x in a), None)

举个例子,

a = []
item = next(x for x in a)

这段代码会抛出一个StopIteration异常;

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
896

Python 2.6+ 和 Python 3:

如果你希望在找不到匹配的元素时抛出 StopIteration 错误:

next(x for x in the_iterable if x > 3)

如果你希望返回一个默认值(比如 None)而不是抛出错误:

next((x for x in the_iterable if x > 3), default_value)

注意,在这种情况下,你需要在生成器表达式外面加一对额外的括号——每当生成器表达式不是唯一的参数时,都需要这样做。

我发现大多数回答都忽略了内置的 next 函数,所以我猜他们可能出于某种神秘的原因,完全专注于 2.5 版本及更早的版本——而没有提到 Python 版本的问题(但我在提到 next 的回答中也没有看到这方面的说明,这就是我觉得有必要自己提供一个答案的原因——至少这样可以记录下“正确版本”的问题;-)。

Python <= 2.5

迭代器的 .next() 方法会立即抛出 StopIteration 错误,如果迭代器立刻结束——也就是说,对于你的使用场景,如果可迭代对象中没有任何项满足条件。如果你不在乎(也就是说,你知道一定会有至少一个符合条件的项),那么就直接使用 .next()(在生成器表达式上使用最佳,适用于 Python 2.6 及更高版本的 next 内置函数)。

如果你 在乎,那么像你在问题中最初提到的那样,把事情封装在一个函数中似乎是最好的选择。虽然你提出的函数实现没问题,但你也可以使用 itertoolsfor...: break 循环、生成器表达式,或者在函数体中使用 try/except StopIteration,正如其他回答所建议的那样。这些替代方案的附加价值不大,所以我建议你使用你最初提出的简单版本。

撰写回答