在列表中查找一个值

917
我使用以下代码来检查 item 是否在 my_list 中:
if item in my_list:
    print("Desired item is in list")

在Python中,"if item in my_list:"是查找列表中元素最“Pythonic”的方式吗?

重新开放编辑:虽然这个问题被认为是重复的,但我并不完全相信:这个问题大致上是“在列表中查找元素的最Pythonic的方法是什么”。而第一个回答真的详细介绍了所有Python的做法。

然而,在链接的重复问题及其对应的答案中,重点仅限于Python中的'in'关键字。我认为这真的很局限,与当前问题相比非常有限。

我认为对于这个当前的问题,答案更加相关和详细,比提出的重复问题/答案的回答更好。


3
如果item等于myList中的某个元素,那就完全没问题并且应该能起作用。 - Niklas B.
1
你的意思是这是正确的做法吗?在我的几次尝试中,可能有空格和换行符干扰...我只是想确保这是实现“在列表中查找”(通常情况下)的正确方式。 - Stephane Rolland
1
很惊奇的是,搜索如何根据条件从列表中提取子集的问题并没有找到这个问题及其良好的答案。也许添加这个评论将允许它在下一次使用这些术语进行搜索时命中单词“提取”和/或“子集”。干杯。 - johnjps111
回滚了最后一个版本,因为接受的答案是基于先前版本的。 - Gert Arnold
这个问题的版本较差,因为“有时它找不到项目”不够清楚。例如,许多人希望'x' in [['a','b'],['x','y']]搜索嵌套列表并评估为True,但它显然没有(x既不等于['a','b']也不等于['x','y'])。由于从未有[MRE]解释“有时”,我们无法知道打算解决什么问题。对于简单的情况,链接的重复问题提出了更好的问题,并且具有权威答案。 - Karl Knechtel
1
@johnjps111,这部分是因为此处的最佳答案提供了许多猜测和未被询问的问题。这不是Stack Overflow的预期工作方式;它不是一个讨论论坛。话虽如此,“提取子集”对我来说听起来像是描述确定列表中哪些元素符合条件的过程的非常奇怪的方式。 - Karl Knechtel
14个回答

1725

对于你的第一个问题:如果item等于my_list中的某个元素,则"if item is in my_list:"是完全可以的,并且应该能够工作。这个元素必须与列表中的某个元素完全匹配。例如,"abc""ABC"不匹配。

特别是浮点数可能会因精度问题而产生错误。例如1 - 1/3 != 2/3

至于你的第二个问题:在列表中查找东西实际上有几种可能的方法。

检查是否存在某个元素

这就是你所描述的用例:检查某个元素是否在列表中。如你所知,可以使用in运算符来实现:

3 in [1, 2, 3] # => True

筛选集合

即查找序列中满足特定条件的所有元素。你可以使用列表推导式或生成器表达式来实现:

matches = [x for x in lst if fulfills_some_condition(x)]
matches = (x for x in lst if x > 6)

后者将返回一个生成器,你可以将其想象成一种懒惰列表,只有当你迭代它时,它才会被构建。顺便说一句,第一个和第二个是完全等价的。

matches = filter(fulfills_some_condition, lst)

在Python 2中,您可以看到高阶函数的运作。在Python 3中,filter不再返回一个列表,而是一个类似生成器的对象。

查找第一次出现

如果您只想要第一个符合条件的内容(但还不知道是什么),可以使用for循环(可能还会使用else子句,这个并不是很有名)。您也可以使用

next(x for x in lst if ...)

使用该方法会返回第一个匹配的结果,如果没有匹配,则会引发StopIteration异常。或者,您可以使用

next((x for x in lst if ...), [default value])

查找元素的位置

对于列表,还有一个index方法,有时候很有用,如果你想知道列表中某个元素的位置:

[1,2,3].index(2) # => 1
[1,2,3].index(4) # => ValueError

需要注意的是,如果存在重复项,.index 总是返回最低的索引值:

[1,2,3,2].index(2) # => 1

如果存在重复值且您希望获取所有索引,则可以使用enumerate()

[i for i,x in enumerate([1,2,3,2]) if x==2] # => [1, 3]

15
Stephane: 让我来改一下说法:“如果x在列表中”不是人们抱怨没有内置函数的东西。他们抱怨的是没有明确的方法在列表中找到与某个条件匹配的第一个出现的东西。但正如我在答案中所述,next()可以用于此(被滥用)。 - Niklas B.
4
第二个代码不会生成元组,而是生成器(实际上是一个尚未构建的列表)。如果您只想使用结果一次,则通常首选生成器。但是,如果您之后要多次使用创建的集合,则最好一开始就创建一个显式列表。请看我的更新,现在它结构更清晰了 :) - Niklas B.
45
您的“查找第一个实例”的示例很棒。感觉比使用[列表推导式...][0]方法更符合Python语言的特点。 - acjay
6
我对Python的“函数式”能力越来越失望。在Haskell中,Data.List模块中有一个叫做find的函数正好可以实现这个功能。但是在Python中却没有,而且它太小了,无法成为一个库,所以你不得不一遍又一遍地重新实现相同的逻辑。真是浪费时间... - user1685095
5
如果index()有一个名为key的关键字参数,可以像max()一样使用,那就太好了。例如:index(list, key=is_prime)。其中is_prime是一个可调用对象。 - Curt
显示剩余11条评论

270

如果你想在使用next方法时查找一个元素或者None,并且希望在列表中没有找到该元素时不抛出StopIteration异常:

first_or_default = next((x for x in lst if ...), None)

2
next 函数的第一个参数应该是迭代器,而列表/元组不是迭代器。因此,正确的用法是 first_or_default = next(iter([x for x in lst if ...]), None),请参考 https://docs.python.org/3/library/functions.html#next。 - Devy
21
@Devy: 没错,但是(x for x in lst if ...)是在列表lst上生成一个生成器(它本身就是一个迭代器)。如果你使用next(iter([x for x in lst if ...]), None),你需要构建列表[x for x in lst if ...],这将是一个更加昂贵的操作。 - Erlend Graff
2
这里有一个抽象定义查找函数的方法。只需将if语句中的布尔表达式封装在lambda中,就可以通常使用find(fn,list)来代替晦涩的生成器代码。 - semiomant

33

虽然Niklas B.的回答已经相当全面,但是当我们想在列表中找到一个项目时,有时候获取其索引是很有用的:

next((i for i, x in enumerate(lst) if [condition on x]), [default value])

你想要简化它一点。 - Random

29

查找第一次出现

在itertools中有一个用于此的函数:

def first_true(iterable, default=False, pred=None):
    """Returns the first true value in the iterable.

    If no true value is found, returns *default*

    If *pred* is not None, returns the first item
    for which pred(item) is true.

    """
    # first_true([a,b,c], x) --> a or b or c or x
    # first_true([a,b], x, f) --> a if f(a) else b if f(b) else x
    return next(filter(pred, iterable), default)

例如,下面的代码在列表中查找第一个奇数:

>>> first_true([2,3,4,5], None, lambda x: x%2==1)
3  

You can copy/paste it or install more-itertools

pip3 install more-itertools

此配方已经包含在哪里。


1
谢谢。这是一个代码示例,但你必须将其复制并粘贴到自己的代码中,这非常愚蠢。为什么他们不直接包含在内呢?Ruby有Enumerable#find,这是一个经典的例子,说明它的库比Python更加用户友好。 - Adam Spiers
1
@AdamSpiers pip install more-itertools 请安装更多迭代工具。 - Antony Hatchkins
3
谢谢,我猜你是指more-itertools.first_true()。了解到这点很好,但仍然很荒谬,因为没有一种优雅的方式可以使用语言或标准库本身实现这一点。使用需要默认值的next hack很繁琐。 - Adam Spiers
据我所知,他们不希望Python变成Lisp或Haskell。拥有完整的函数工具范围会使用Python编写的程序像函数式语言一样难以阅读。然而,我个人也很想在语言或标准库中使用这些函数。 - Antony Hatchkins
2
如果这是他们使用的理由,那根本毫无道理。Ruby并没有通过将最常用的方法包含在其标准库中而变成Lisp或Haskell,而且,依我之见,函数式语言可以非常易读,通常比命令式语言更容易理解。但是,无论如何,我不应该在这里引发一场语言战;-) - Adam Spiers
1
@AdamSpiers 我并不100%确定他们没有其他动机,这只是我所知道的唯一理由。我发现Ruby语法比Python的可读性差。你知道,如果你包括所有来自函数式语言的关键字,下一个问题将是“为什么相同的结构在Python中运行的速度比Haskell慢x倍”。不包括它们只是一个暗示,如果你喜欢它们,也许Python不是用来编写它们的正确语言 ;) 可读性首先取决于作者。Python只是努力让那些喜欢写难以阅读代码的人的生活变得更加困难 :) - Antony Hatchkins

10

另一种选择:您可以使用if item in list:检查一个项目是否在列表中,但这是O(n)的顺序。如果您处理大量项目的列表,并且您只需要知道是否有东西是列表的成员,则可以先将列表转换为集合,利用常数时间设置查找

my_set = set(my_list)
if item in my_set:  # much faster on average than using a list
    # do something

并非在所有情况下都是正确的解决方案,但对于某些情况,这可能会使您获得更好的性能。

请注意,使用set(my_list)创建集合也是O(n),因此如果您只需要执行一次,则用这种方式不会更快。 如果您需要重复检查成员身份,那么在初始集合创建之后,每次查找都将为O(1)。


6

定义和用法

count() 方法返回具有指定值的元素数量。

语法

list.count(value)

例子:

fruits = ['apple', 'banana', 'cherry']

x = fruits.count("cherry")

问题示例:

item = someSortOfSelection()

if myList.count(item) >= 1 :

    doMySpecialFunction(item)

5
在一个非常长的列表中,比如一百万个项目,这个方法是否有效? - 3kstc

4

在处理字符串列表时,您可能需要使用以下两种搜索方法之一:

  1. 如果列表元素等于某个项(例如,'example' 在 ['one','example','two'] 中):

    if item in your_list: some_function_on_true()

    'ex' in ['one','ex','two'] => True

    'ex_1' in ['one','ex','two'] => False

  2. 如果列表元素类似于某个项(例如,'ex' 在 ['one,'example','two'] 中或 'example_1' 在 ['one','example','two'] 中):

    matches = [el for el in your_list if item in el]

    或者

    matches = [el for el in your_list if el in item]

    然后只需检查len(matches)或根据需要阅读它们。


4

for循环

def for_loop(l, target):
    for i in l:
        if i == target:
            return i
    return None


l = [1, 2, 3, 4, 5]
print(for_loop(l, 0))
print(for_loop(l, 1))
# None
# 1

下一步

def _next(l, target):
    return next((i for i in l if i == target), None)


l = [1, 2, 3, 4, 5]
print(_next(l, 0))
print(_next(l, 1))
# None
# 1

more_itertools

more_itertools.first_true(iterable, default=None, pred=None)

安装

pip install more-itertools

或直接使用它。
def first_true(iterable, default=None, pred=None):
    return next(filter(pred, iterable), default)

from more_itertools import first_true

l = [1, 2, 3, 4, 5]
print(first_true(l, pred=lambda x: x == 0))
print(first_true(l, pred=lambda x: x == 1))
# None
# 1

比较

方法 时间/s
for循环 2.77
next()函数 3.64
more_itertools.first_true()函数 3.82或10.86
import timeit
import more_itertools


def for_loop():
    for i in range(10000000):
        if i == 9999999:
            return i
    return None


def _next():
    return next((i for i in range(10000000) if i == 9999999), None)


def first_true():
    return more_itertools.first_true(range(10000000), pred=lambda x: x == 9999999)


def first_true_2():
    return more_itertools.first_true((i for i in range(10000000) if i == 9999999))


print(timeit.timeit(for_loop, number=10))
print(timeit.timeit(_next, number=10))
print(timeit.timeit(first_true, number=10))
print(timeit.timeit(first_true_2, number=10))
# 2.7730861
# 3.6409407000000003
# 10.869996399999998
# 3.8214487000000013

1
我已经完成了与上面相同的测试,但还额外添加了以下内容:def first_true_2(): return more_itertools.first_true((i for i in range(10_000_000) if i == 9_999_999))...这实际上最终时间最快,或者至少在变化范围内是可比较的。 我的 观点first_true 具有更易于阅读的方法接口,而可读性很重要。 - MrSpaceman

4

如果你只需要一次性检查集合中是否存在某个值,使用'in'运算符就可以了。但是,如果你需要检查多次的话,我建议使用bisect模块。请记住,使用bisect模块之前必须对数据进行排序。因此,你需要先对数据进行排序,然后再使用bisect。在我的电脑上,使用bisect模块比使用'in'运算符快约12倍。

以下是使用Python 3.8及以上版本语法的示例代码:

import bisect
from timeit import timeit

def bisect_search(container, value):
    return (
      (index := bisect.bisect_left(container, value)) < len(container) 
      and container[index] == value
    )

data = list(range(1000))
# value to search
true_value = 666
false_value = 66666

# times to test
ttt = 1000

print(f"{bisect_search(data, true_value)=} {bisect_search(data, false_value)=}")

t1 = timeit(lambda: true_value in data, number=ttt)
t2 = timeit(lambda: bisect_search(data, true_value), number=ttt)

print("Performance:", f"{t1=:.4f}, {t2=:.4f}, diffs {t1/t2=:.2f}")

输出:

bisect_search(data, true_value)=True bisect_search(data, false_value)=False
Performance: t1=0.0220, t2=0.0019, diffs t1/t2=11.71

从技术上讲,这只是部分回答了原始问题,但仍然是我最喜欢的答案,因为bisect.bisect_left/right(..., key=...)解决了许多与此页面相关的问题。 - casper.dcl

3

不要使用list.index(x),因为它会返回x在列表中的索引值,如果x没有被找到,则会返回一个#ValueError错误消息。相反,你可以使用list.count(x),它将返回x在列表中出现的次数(验证x是否确实在列表中),否则将返回0(如果列表中没有x)。count()的好处在于,当x未被找到时,它不会破坏你的代码或需要你抛出异常。


4
坏处在于它会“计算”元素。它不会在找到元素时停止。因此,在处理大型列表时性能较差。 - Jean-François Fabre

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接