在 python 中处理 list.index (可能不存在)的最佳方式?

我的代码是这样的:

thing_index = thing_list.index(thing)
otherfunction(thing_list, thing_index)

好的,这很简单,但是你们明白了。现在 thing实际上可能不在列表中,在这种情况下,我希望将 -1作为 thing_index传递。在其他语言中,如果 index()找不到元素,那么它就会返回这个结果。事实上,它抛出了一个 ValueError

我可以这么做:

try:
thing_index = thing_list.index(thing)
except ValueError:
thing_index = -1
otherfunction(thing_list, thing_index)

但这感觉肮脏,加上我不知道 ValueError是否可以提高其他原因。基于生成器函数,我想出了以下解决方案,但它似乎有点复杂:

thing_index = ( [(i for i in xrange(len(thing_list)) if thing_list[i]==thing)] or [-1] )[0]

有没有一种更简洁的方法来实现同样的目标? 让我们假设列表没有排序。

151113 次浏览

使用 try-but 子句没有什么“脏”的地方。这是蟒蛇的方式。ValueError将只由 .index方法引发,因为它是您在那里拥有的唯一代码!

回答这个问题:
在 Python 中,请求原谅比得到许可更容易的理念已经确立,而且 没有 index不会为任何其他问题提出这种类型的错误。我想不出来。

< CODE > dict type有一个 函数,如果字典中没有这个键,那么 get的第二个参数就是它应该返回的值。类似地,还有 < CODE > setdefault ,如果键存在,它将返回 dict中的值,否则它将根据默认参数设置值,然后返回默认参数。

您可以扩展 list类型,使其具有 getindexdefault方法。

class SuperDuperList(list):
def getindexdefault(self, elem, default):
try:
thing_index = self.index(elem)
return thing_index
except ValueError:
return default

然后可以像这样使用:

mylist = SuperDuperList([0,1,2])
index = mylist.getindexdefault( 'asdf', -1 )

我不知道为什么你会认为它是肮脏的... 因为例外?如果你想要一个俏皮话,这就是:

thing_index = thing_list.index(elem) if thing_list.count(elem) else -1

但我建议不要使用它; 我认为 Ross Rogers 的解决方案是最好的,使用对象来封装你想要的行为,不要试图以牺牲可读性为代价将语言推向极限。

使用 ValueError的代码没有任何问题。如果你想避免异常,这里还有另一个一行程序:

thing_index = next((i for i, x in enumerate(thing_list) if x == thing), -1)

这是一个语言哲学问题。例如,在 Java 中一直有一个传统,即异常应该只在发生错误的“异常情况”中使用,而不是在 流量控制中使用。最初这是出于性能原因,因为 Java 异常比较慢,但是现在这已经成为可以接受的风格。

相比之下,Python 总是使用异常来指示正常的程序流,比如我们在这里讨论的引发 ValueError。在 Python 风格中没有什么“肮脏”的东西,而且还有更多这样的东西。一个更常见的例子是 StopIteration异常,它是由迭代器的 next()方法引发的,用来表示没有进一步的值。

thing_index = thing_list.index(elem) if elem in thing_list else -1

就一句,很简单,没有例外。

这个怎么样:

otherfunction(thing_collection, thing)

与其在函数接口中公开与实现相关的内容,比如列表索引,不如传递集合和内容,让其他函数处理“成员资格测试”问题。如果其他函数被写成集合类型不可知的,那么它可能以:

if thing in thing_collection:
... proceed with operation on thing

如果 thing _ Collection 是一个 list、 tuple、 set 或 dict,那么它将工作。

这可能比:

if thing_index != MAGIC_VALUE_INDICATING_NOT_A_MEMBER:

这是你在其他函数中已经有的代码。

我建议:

if thing in thing_list:
list_index = -1
else:
list_index = thing_list.index(thing)

我也有同样的问题。索引()”方法。对于它抛出异常这一事实我没有异议,但是我强烈反对它是一个非描述性 ValueError 这一事实。如果是 IndexError 我也能理解。

我可以理解为什么返回“-1”也是一个问题,因为它在 Python 中是一个有效的索引。但实际上,我 永远不会期望一个“。Index ()”方法返回负数。

这里有一行代码(好吧,这是一条相当长的代码行... ...) ,只在列表中查看一次,如果没有找到该项,则返回“ Nothing”。如果您希望返回 -1,那么将其重写为 -1是微不足道的。

indexOf = lambda list, thing: \
reduce(lambda acc, (idx, elem): \
idx if (acc is None) and elem == thing else acc, list, None)

使用方法:

>>> indexOf([1,2,3], 4)
>>>
>>> indexOf([1,2,3], 1)
0
>>>

如果你经常这样做,那么最好把它放在一个 helper 函数中:

def index_of(val, in_list):
try:
return in_list.index(val)
except ValueError:
return -1

这样怎么样:

temp_inx = (L + [x]).index(x)
inx = temp_inx if temp_inx < len(L) else -1

这个怎么样:

li = [1,2,3,4,5] # create list


li = dict(zip(li,range(len(li)))) # convert List To Dict
print( li ) # {1: 0, 2: 1, 3: 2, 4: 3, 5: 4}
li.get(20) # None
li.get(1)  # 0

实施情况比较

Python 3.8的简单比较

除了数组(n < 100)有许多未命中的情况外,DR maybeidx2通常更快

def maybeidx1(l, v):
return l.index(v) if v in l else None


def maybeidx2(l, v):
try:
return l.index(v)
except ValueError:
return None

测试案例:

a = [*range(100_000)]
# Case 1: index in list
maybeidx1(a, 50_000)
Out[20]: 50000
maybeidx2(a, 50_000)
Out[21]: 50000
# Case 2: index not in list
maybeidx1(a, 100_000) is None
Out[23]: True
maybeidx2(a, 100_000) is None
Out[24]: True

时间情况1

%timeit maybeidx1(a, 50_000)
1.06 ms ± 15.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit maybeidx2(a, 50_000)
530 µs ± 8.47 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

时间情况2

%timeit maybeidx1(a, 100_000)
1.07 ms ± 21 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit maybeidx2(a, 100_000)
1.07 ms ± 16.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

结果

对于较大的数组使用 maybeidx2方法。这是更快的原因,因为 maybeidx1有两个数组扫描在搜索值-这仍然是 O (n)时间,但有一个常数乘法器2,因此在实践中更慢。在列表中存在值的情况下,这一点是成立的。当值不存在时,这些时间大致相等; 它们都必须精确地扫描整个数组一次,然后返回 Nonetry-except的开销是可以忽略不计的,即使有一个10-除非的阵列大小的情况下2发生。然后,try-except的开销是显而易见的。例如:

a = [*range(10)]
%timeit maybeidx1(a, 10)
191 ns ± 2.61 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%timeit maybeidx2(a, 10)
566 ns ± 5.93 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

a有超过100个元素时,这个开销(在我的机器上)变得可以忽略不计。

虽然已经有一段时间了,但是它是 stdlib 的核心部分,并且有几十个潜在的方法,所以我认为为不同的建议提供一些基准测试并包含 numpy 方法是非常有用的,因为它是目前为止最快的方法。

import random
from timeit import timeit
import numpy as np


l = [random.random() for i in range(10**4)]
l[10**4 - 100] = 5


# method 1
def fun1(l:list, x:int, e = -1) -> int:
return [[i for i,elem in enumerate(l) if elem == x] or [e]][0]


# method 2
def fun2(l:list, x:int, e = -1) -> int:
for i,elem in enumerate(l):
if elem == x:
return i
else:
return e


# method 3
def fun3(l:list, x:int, e = -1) -> int:
try:
idx = l.index(x)
except ValueError:
idx = e
return idx


# method 4
def fun4(l:list, x:int, e = -1) -> int:
return l.index(x) if x in l else e


l2 = np.array(l)
# method 5
def fun5(l:list or np.ndarray, x:int, e = -1) -> int:
res = np.where(np.equal(l, x))
if res[0].any():
return res[0][0]
else:
return e




if __name__ == "__main__":
print("Method 1:")
print(timeit(stmt = "fun1(l, 5)", number = 1000, globals = globals()))
print("")
print("Method 2:")
print(timeit(stmt = "fun2(l, 5)", number = 1000, globals = globals()))
print("")
print("Method 3:")
print(timeit(stmt = "fun3(l, 5)", number = 1000, globals = globals()))
print("")
print("Method 4:")
print(timeit(stmt = "fun4(l, 5)", number = 1000, globals = globals()))
print("")
print("Method 5, numpy given list:")
print(timeit(stmt = "fun5(l, 5)", number = 1000, globals = globals()))
print("")
print("Method 6, numpy given np.ndarray:")
print(timeit(stmt = "fun5(l2, 5)", number = 1000, globals = globals()))
print("")

当以 main 方式运行时,会在我的机器上打印出以下内容,指示完成每个功能1000次试验的时间(以秒为单位) :

方法一: 0.750210279990098

方法二: 0.7291318440002215

方法三: 0.24142152300009911

方法四: 0.5253471979995084

方法5,数字给定列表: 0.5045417560013448

方法6,numpy 给定 np.ndarray: 0.011147511999297421

当然,这个问题特别提到了列表,所以最好的解决方案是使用 try- 例外的方法,然而,使用 numpy 的數据结构和操作符而不是 python 的數据结构所提供的速度改善(至少是 try-除外方法的20倍)是非常重要的,如果在很多數据阵列上建立一些对性能至关重要的东西,那么作者应该尝试使用 numpy 来充分利用超快的 C 绑定。(CPython 解释器,其他解释器的性能可能有所不同)

顺便说一下,方法5比方法6慢很多的原因是 numpy 首先要把给定的列表转换成它自己的 numpy 数组,所以给它一个列表不会破坏它,只是没有充分利用可能的速度。

我将直言不讳: 这里的答案非常糟糕,而且具有疯狂的时间复杂性。

有个简单的办法。

对于 dict().get('key', 'some_value'),将返回 'key'处的值,如果字典中没有该键,则返回 'some_value'

您可以使用列表及其索引创建这样的字典。

mylist = ['cat' 'dog', 'bunny']


mapping = {value: index for index, value in enumerate(mylist)}

然后,如果找到了,mapping.get('key', 0)将返回索引,或者 None

mapping.get('penguin', 0)  # returns 0

自 Python 3.6以来,indexrindexfindrfind方法,它们返回 -1而不是抛出异常。