在列表中查找项目的索引

4349

如何获取列表["foo", "bar", "baz"]中元素"bar"的索引1


14
你是要返回:[1] 如果有多个 "bar" 实例,则返回最低的索引, [2] 所有 "bar" 的索引? - Ṃųỻịgǻňạcểơửṩ
6
a) 这个项目是否保证在列表中存在,如果不存在怎么处理错误情况?(返回None/抛出ValueError) b) 列表条目是否保证唯一,并且我们应该返回匹配项的第一个索引还是所有索引? - smci
1
通过numpy集成查看答案,numpy数组比Python列表更有效率。如果列表很短,从Python列表中复制它没有问题,但如果不是,也许你应该考虑一开始就将元素存储在numpy数组中。 - Athanassios
我投票关闭这个问题(以抗议),因为已经有42个未删除的答案(还有16个被删除的),针对一个简单的一行参考问题,几乎所有答案都有相同的核心内置函数(因为这是解决问题唯一合理和明智的方法,而且周围的一切都只是错误检查或创造性地重新解释规范,这仍然只留下另一种合理、明智的方法来解决扩展问题)。 - Karl Knechtel
未来版本的Python中没有实现更好方法的现实机会,因为现有方法已经是调用列表中单个内置方法,最简单的方式。 - Karl Knechtel
46个回答

5926
>>> ["foo", "bar", "baz"].index("bar")
1

请查看文档,了解列表的内置.index()方法:

list.index(x[, start[, end]])

Return zero-based index in the list of the first item whose value is equal to x. Raises a ValueError if there is no such item.

The optional arguments start and end are interpreted as in the slice notation and are used to limit the search to a particular subsequence of the list. The returned index is computed relative to the beginning of the full sequence rather than the start argument.

注意事项

在列表长度方面具有线性时间复杂度

index 调用按顺序检查列表的每个元素,直到找到匹配项。如果列表很长,并且没有保证值靠近开头,这可能会减慢代码速度。

只能通过使用不同的数据结构完全避免此问题。但是,如果已知要素位于列表的某个部分中,则可以使用 startend 参数来缩小搜索范围。

例如:

>>> import timeit
>>> timeit.timeit('l.index(999_999)', setup='l = list(range(0, 1_000_000))', number=1000)
9.356267921015387
>>> timeit.timeit('l.index(999_999, 999_990, 1_000_000)', setup='l = list(range(0, 1_000_000))', number=1000)
0.0004404920036904514

第二次调用速度快了几个数量级,因为它只需要搜索10个元素,而不是全部100万个。

仅返回第一个匹配项的索引

调用index会按顺序在列表中搜索,直到找到匹配项,并在那里停止。如果该值可能有多个出现位置并且需要所有索引,则index无法解决问题:
>>> [1, 1].index(1) # the `1` index is not found.
0

相反,使用列表推导式或生成器表达式进行搜索, 使用 enumerate获取索引:

>>> # A list comprehension gives a list of indices directly:
>>> [i for i, e in enumerate([1, 2, 1]) if e == 1]
[0, 2]
>>> # A generator comprehension gives us an iterable object...
>>> g = (i for i, e in enumerate([1, 2, 1]) if e == 1)
>>> # which can be used in a `for` loop, or manually iterated with `next`:
>>> next(g)
0
>>> next(g)
2

列表推导式和生成器表达式技术仍然适用于只有一个匹配项的情况,并且更具普适性。
如果没有匹配项,会引发异常。
如上面的文档中所述,使用.index将在搜索的值不在列表中时引发异常:
>>> [1, 1].index(2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: 2 is not in list

如果这是一个问题,可以使用item in my_list进行显式检查,或者根据需要使用try/except处理异常。 显式检查简单易懂,但必须再次迭代列表。有关此选择的更多指导,请参见Python中的EAFP原则是什么?

43
index函数返回第一个值为"bar"的元素的索引位置。如果列表中出现两个相同的"bar",则无法找到第二个"bar"的索引值。请参阅文档:https://docs.python.org/3/tutorial/datastructures.html - Having a life on a beach
10
如果您只需要查找一个元素(第一个元素),我发现使用index()函数比用列表推导式在整数列表中进行查找快了将近90%。 - slybloty
2
如果列表非常长,应该使用什么数据结构? - izhang05
1
@jvel07,请查看我回答中的列表/生成器推导式示例。 - Alex Coventry
1
事后看来,引发异常似乎是一个不太好的设计选择。 - Eric Walker
显示剩余3条评论

710

大多数答案都解释了如何找到单个索引,但是如果该项在列表中出现多次,则它们的方法不会返回多个索引。使用enumerate()

for i, j in enumerate(['foo', 'bar', 'baz']):
    if j == 'bar':
        print(i)
index() 函数仅返回第一个匹配项,而 enumerate() 则返回所有匹配项。

作为列表推导式:

[i for i, j in enumerate(['foo', 'bar', 'baz']) if j == 'bar']

这里还有另一个使用 itertools.count() 的小型解决方案(与enumerate几乎相同):

from itertools import izip as zip, count # izip for maximum efficiency
[i for i, j in zip(count(), ['foo', 'bar', 'baz']) if j == 'bar']

对于较大的列表,与使用 enumerate() 相比,这更加高效:

$ python -m timeit -s "from itertools import izip as zip, count" "[i for i, j in zip(count(), ['foo', 'bar', 'baz']*500) if j == 'bar']"
10000 loops, best of 3: 174 usec per loop
$ python -m timeit "[i for i, j in enumerate(['foo', 'bar', 'baz']*500) if j == 'bar']"
10000 loops, best of 3: 196 usec per loop

3
对于我来说,枚举比基于索引的方法更有效,因为我想使用 "startswith" 收集字符串的索引,并且需要收集多个出现次数。或者有没有一种方法可以在“startswith”中使用索引,而我却无法找到。 - Tupelo Thistlehead
9
在我的手中,列举版本始终略微更快。自上述测量发布以来,可能已更改一些实现细节。 - Alex Coventry
4
这个问题早在2011年就已经得到了回答:https://dev59.com/qG015IYBdhLWcg3w9wh0 - Cristik
在Python 3中,应该使用内置的zip替换izip。请参见此处 - UnusualWays
1
这是一个很好的解决方案,比已接受的解决方案更加灵活。例如,如果您只希望在列表中有1个值,则可以添加一个if语句来引发异常if len([i for i, j in enumerate(['foo', 'bar', 'baz']) if j == 'bar']) > 1,否则您可以返回[i for i, j in enumerate(['foo', 'bar', 'baz']) if j == 'bar'][0] - Brian Keith

240
获取所有索引:

要获取所有的索引:

indexes = [i for i, x in enumerate(xs) if x == 'foo']

7
这个问题已经有另外一个问答了,于2011年添加在以下链接中:https://dev59.com/qG015IYBdhLWcg3w9wh0 - Cristik

156

index() 返回值的第一个索引!

| index(...)
| L.index(value, [start, [stop]]) -> integer -- 返回值的第一个索引

def all_indices(value, qlist):
    indices = []
    idx = -1
    while True:
        try:
            idx = qlist.index(value, idx+1)
            indices.append(idx)
        except ValueError:
            break
    return indices

all_indices("foo", ["foo","bar","baz","foo"])

3
如果列表中不存在该项怎么办? - Peter Mortensen
1
不存在的项目将引发 ValueError。 - Nam G VU
1
这个答案更适合放在这里:https://dev59.com/qG015IYBdhLWcg3w9wh0 - Cristik

109
a = ["foo","bar","baz",'bar','any','much']

indexes = [index for index in range(len(a)) if a[index] == 'bar']

108

如果元素不在列表中,将会出现问题。这个函数处理此问题:

# if element is found it returns index of element else returns None

def find_element_in_list(element, list_element):
    try:
        index_element = list_element.index(element)
        return index_element
    except ValueError:
        return None

74
你需要设置一个条件来检查你正在搜索的元素是否在列表中。
if 'your_element' in mylist:
    print mylist.index('your_element')
else:
    print None

2
这有助于我们避免使用try catch! - devssh
4
然而,这可能会使复杂度增加一倍。有人进行过检查吗? - stefanct
2
@stefanct 时间复杂度仍然是线性的,但它会遍历列表两次。 - ApproachingDarknessFish
1
@ApproachingDarknessFish 显然那就是我想说的。即使严谨地说,这两种算法的复杂度是一样的,但是在许多使用情况下,迭代两次可能是一个严重的劣势,因此我提出了这个问题。我们仍然不知道答案…… - stefanct
@stefanct 这可能会使复杂度加倍,我认为列表上的'in'运算符具有线性运行时间。 @ApproachingDarknessFish 表示它将迭代两次,这回答了你的问题,并且正确地说将线性复杂度加倍并不是一个很大的问题。 在许多用例中,我不会认为两次迭代列表是一个严重的缺点,因为复杂度理论告诉我们O(n) + O(n) -> O(2*n) -> O(n),即-变化通常是微不足道的。 - Matthew Strasiotto

62

如果你想要所有索引,你可以使用NumPy

import numpy as np

array = [1, 2, 1, 3, 4, 5, 1]
item = 1
np_array = np.array(array)
item_index = np.where(np_array==item)
print item_index
# Out: (array([0, 2, 6], dtype=int64),)

这是一个清晰易读的解决方案。


5
字符串列表、非数字对象列表等怎么办? - András Aszódi
2
这个答案最好发布在这里:https://dev59.com/qG015IYBdhLWcg3w9wh0 - Cristik
1
这是我读过的最好的文章。numpy数组比Python列表更高效。如果列表很短,从Python列表中复制它没有问题,但如果不是,开发者应该考虑一开始就将元素存储在numpy数组中。 - Athanassios

56

在Python中给定包含项的列表,查找其索引

对于一个列表 ["foo", "bar", "baz"] 并且该列表中有一项 "bar",最简洁的方法是什么来获取它的索引(1)?

好吧,当然有index()方法,它返回第一次出现的索引:

>>> l = ["foo", "bar", "baz"]
>>> l.index('bar')
1

这种方法存在一些问题:

  • 如果值不在列表中,则会出现ValueError的错误
  • 如果列表中有多个相同的值,则只返回第一个值的索引

没有匹配值

如果值可能不存在,您需要捕获ValueError

可以通过以下可重复使用的定义来实现:

def index(a_list, value):
    try:
        return a_list.index(value)
    except ValueError:
        return None

并像这样使用:

>>> print(index(l, 'quux'))
None
>>> print(index(l, 'bar'))
1

这样做的缺点是你可能需要检查返回值 is 或者 is not None:

result = index(a_list, value)
if result is not None:
    do_something(result)

列表中有多个值

如果列表中存在多个相同的值,使用list.index无法获得完整信息:

>>> l.append('bar')
>>> l
['foo', 'bar', 'baz', 'bar']
>>> l.index('bar')              # nothing at index 3?
1

你可以将索引值列举到列表推导式中:

>>> [index for index, v in enumerate(l) if v == 'bar']
[1, 3]
>>> [index for index, v in enumerate(l) if v == 'boink']
[]

如果您没有任何事件发生,您可以通过对结果进行布尔检查来检查,或者如果您循环遍历结果,则可以什么也不做:

indexes = [index for index, v in enumerate(l) if v == 'boink']
for index in indexes:
    do_something(index)

使用pandas更好地处理数据

如果您使用pandas,可以使用Series对象轻松获取此信息:

>>> import pandas as pd
>>> series = pd.Series(l)
>>> series
0    foo
1    bar
2    baz
3    bar
dtype: object

比较检查将返回一系列布尔值:

>>> series == 'bar'
0    False
1     True
2    False
3     True
dtype: bool

将那一系列的布尔值通过下标符号传递给该系列,您将只获取匹配的成员:

>>> series[series == 'bar']
1    bar
3    bar
dtype: object

如果你只想要索引,那么index属性会返回一系列整数:

>>> series[series == 'bar'].index
Int64Index([1, 3], dtype='int64')

如果您想将它们放在列表或元组中,只需将它们传递给构造函数:

>>> list(series[series == 'bar'].index)
[1, 3]

是的,您可以使用带有enumerate的列表推导式,但在我看来,这并不那么优雅-你正在Python中进行等式测试,而不是让用C编写的内置代码处理它:

>>> [i for i, value in enumerate(l) if value == 'bar']
[1, 3]

这是一个XY问题吗?

XY问题是指询问关于您尝试解决的问题而不是您实际问题本身。

你觉得你需要列表中元素的索引吗?

如果你已经知道了值,你为什么要在列表中找到它的位置呢?

如果该值不存在,则捕获ValueError会非常繁琐 - 我倾向于避免这种情况。

我通常都在迭代列表,所以我通常会保留任何有趣信息的指针,并使用enumerate来获取索引.

如果你正在处理数据,你应该使用pandas - 它比我展示的纯Python解决方案更加优雅。

我不记得自己需要 list.index。 但是,我已经浏览了Python标准库,并发现了一些很好的用法。

idlelib中有许多用途,用于GUI和文本解析。

keyword模块使用它在模块中查找注释标记,通过元编程自动重新生成其中的关键字列表。

在Lib/mailbox.py中,它似乎像有序映射一样使用它:

key_list[key_list.index(old)] = new

并且

del key_list[key_list.index(key)]

在 Lib/http/cookiejar.py 中,该代码似乎用于获取下一个月份:

mon = MONTHS_LOWER.index(mon.lower())+1

在Lib/tarfile.py中,类似于distutils的方法可以获取到某个项之前的切片:

members = members[:members.index(tarinfo)]

在Lib/pickletools.py中:
numtopop = before.index(markobject)

这些用法似乎有一个共同点,就是它们似乎在操作受限大小的列表(由于list.index的O(n)查找时间而很重要),并且它们在解析中被广泛使用(在Idle的情况下也包括UI)。
虽然有用例,但它们相对较少。如果你发现自己正在寻找这个答案,请问一下自己是否正在使用语言提供的工具直接解决你的问题。

55

这里提出的所有功能都能够重现内在的语言行为,但会掩盖正在发生的事情。

[i for i in range(len(mylist)) if mylist[i]==myterm]  # get the indices

[each for each in mylist if each==myterm]             # get the items

mylist.index(myterm) if myterm in mylist else None    # get the first index and fail quietly

如果编程语言本身提供了所需的方法,为什么还要编写带有异常处理的函数呢?


10
第三种方法会对列表进行两次迭代,对吗? - Eric Duminil
1
回复:“这里提出的所有函数”:在撰写时也许是这样,但你应该检查更新的答案以确定是否仍然正确。 - Peter Mortensen

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