在python中处理list.index(可能不存在)的最佳方法?
Posted
技术标签:
【中文标题】在python中处理list.index(可能不存在)的最佳方法?【英文标题】:Best way to handle list.index(might-not-exist) in python? 【发布时间】:2011-01-09 02:47:50 【问题描述】:我的代码看起来像这样:
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]
有没有更简洁的方法来实现同样的目标?假设列表没有排序。
【问题讨论】:
"...在这种情况下,我想将 -1 作为thing_index
传递。" - 这绝对不是 Pythonic。在操作不成功的情况下传递(无意义的)令牌值是不受欢迎的 - 异常在这里确实是正确的方法。特别是因为thing_list[-1]
是一个有效的表达式,表示列表中的最后一个条目。
@jellybean: facepalm...发现 java 编码器:P
@Tim:有str.find
方法可以做到这一点:当在主题中找不到针时返回-1
。
@Tim None 会更好......这类似于 dict[key] vs dict.get[key]
@SilentGhost:嗯,很有趣。我可能需要更详细地研究这一点。如果没有找到搜索字符串,str.index()
会抛出异常。
【参考方案1】:
从 Python 3.6 开始,index
和 rindex
有 find
和 rfind
方法,它们返回 -1
而不是抛出异常。
【讨论】:
【参考方案2】:直言不讳:这里的答案非常糟糕,而且时间复杂度高得离谱。
这是一个简单的方法。
使用dict().get('key', 'some_value')
,将返回'key'
处的值,如果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
【讨论】:
我会直言不讳:: 你的代码不起作用。您需要将 [] 替换为 以构建字典。【参考方案3】:如果您经常这样做,那么最好将其存放在辅助函数中:
def index_of(val, in_list):
try:
return in_list.index(val)
except ValueError:
return -1
【讨论】:
别忘了-1
是一个有效的索引:in_list[-1]
,虽然显然不是从index()
返回的。可能返回None
?【参考方案4】:
已经有一段时间了,但它是标准库的核心部分,并且有许多潜在的方法,所以我认为为不同的建议提供一些基准并包括迄今为止最快的 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.7502102799990098
方法二: 0.7291318440002215
方法三: 0.24142152300009911
方法四: 0.5253471979995084
方法5,numpy给定列表: 0.5045417560013448
方法6,numpy给定np.ndarray: 0.011147511999297421
当然,这个问题专门询问列表,所以最好的解决方案是使用 try-except 方法,但是通过使用 numpy 数据结构和运算符而不是python 数据结构很重要,如果在许多数据数组上构建对性能至关重要的东西,那么作者应该尝试在整个过程中使用 numpy 来利用超快的 C 绑定。 (CPython解释器,其他解释器性能可能有所不同)
顺便说一句,方法 5 比方法 6 慢得多的原因是因为 numpy 首先必须将给定的列表转换为它自己的 numpy 数组,所以给它一个列表并不会破坏它,只是没有充分利用速度可能。
【讨论】:
【参考方案5】:实现比较
Python 3.8 上的简单比较
TL;DR maybeidx2
通常更快,但有很多未命中的数组 (n
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,因此在实践中速度较慢。这适用于列表中存在值的情况。当该值不存在时,这些时间将大致相等;他们都必须准确地扫描整个数组一次,然后返回None
。 try-except
的开销可以忽略不计,即使数组大小为 10 - 除非 发生第二种情况。然后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 个元素时(在我的机器上)这个开销可以忽略不计。
【讨论】:
【参考方案6】:这个呢?:
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
【讨论】:
这正是我想要的:如果找不到元素,则获取默认值 - 为此编写函数或 if-else 似乎有点过头了。【参考方案7】:这样怎么样:
temp_inx = (L + [x]).index(x)
inx = temp_inx if temp_inx < len(L) else -1
【讨论】:
【参考方案8】:dict
type 有一个get
function,如果字典中不存在该键,get
的第二个参数是它应该返回的值。同样有setdefault
,如果key存在则返回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 )
【讨论】:
【参考方案9】:我对列表中的“.index()”方法也有同样的问题。我对它抛出异常这一事实没有意见,但我强烈不同意它是一个非描述性的 ValueError 的事实。不过,我可以理解它是否会是一个 IndexError。
我明白为什么返回“-1”也是一个问题,因为它是 Python 中的有效索引。但实际上,我从不期望“.index()”方法返回负数。
这里有一个单行(好吧,这是一个相当长的行...),只遍历列表一次,如果找不到该项目,则返回“None”。如果您愿意,将其重写为返回 -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
>>>
【讨论】:
【参考方案10】:我建议:
if thing in thing_list:
list_index = -1
else:
list_index = thing_list.index(thing)
【讨论】:
此解决方案的问题是“-1”是列表中的有效索引(最后一个索引;倒数第一个)。处理此问题的更好方法是在您的条件的第一个分支中返回 False。【参考方案11】:这个呢:
otherfunction(thing_collection, thing)
与其在函数接口中公开像列表索引这样依赖于实现的东西,不如传递集合和事物,让其他函数处理“成员资格测试”问题。如果其他函数被编写为与集合类型无关,那么它可能会以:
if thing in thing_collection:
... proceed with operation on thing
如果 thing_collection 是列表、元组、集合或字典,这将起作用。
这可能比:
if thing_index != MAGIC_VALUE_INDICATING_NOT_A_MEMBER:
这是你在其他函数中已有的代码。
【讨论】:
【参考方案12】:这个问题是语言哲学问题之一。例如,在 Java 中,一直有一个传统,即异常应该只用于“异常情况”,即发生错误时,而不是flow control。起初这是出于性能原因,因为 Java 异常很慢,但现在这已成为公认的风格。
相比之下,Python 一直使用异常来表示正常的程序流程,比如我们在这里讨论的引发ValueError
。在 Python 风格中,这并没有什么“肮脏”的地方,而且还有更多来自哪里。一个更常见的例子是StopIteration
exception,它由迭代器的next()
方法引发,表示没有其他值。
【讨论】:
实际上,JDK 抛出了太多方式 的检查异常,所以我不确定这种哲学是否真的适用于 Java。我对StopIteration
本身没有问题,因为它明确定义了异常的含义。 ValueError
有点太笼统了。
我指的是不应将异常用于流控制的想法:c2.com/cgi/wiki?DontUseExceptionsForFlowControl,与其说是 Java 具有的检查异常的数量,不如说是其他讨论:mindview.net/Etc/Discussions/CheckedExceptions跨度>
【参考方案13】:
thing_index = thing_list.index(elem) if elem in thing_list else -1
一行。简单的。没有例外。
【讨论】:
简单,是的,但这将进行两次线性搜索,虽然性能本身不是问题,但这似乎过分了。 @Draemon:同意——这将执行 2 次传递——但从一千行代码库来看,这不太可能成为瓶颈。 :) 人们总是可以选择加入for
的命令式解决方案。
带 lambda indexOf = lambda item,list_ : list_.index(item) if item in list_ else -1 # OR None
【参考方案14】:
使用ValueError
的代码没有任何问题。如果您想避免异常,这里还有另一个单行:
thing_index = next((i for i, x in enumerate(thing_list) if x == thing), -1)
【讨论】:
那是python 2.6吗?我知道我没有提到它,但我使用的是 2.5。这可能是我在 2.6 中所做的 @Draemon:是的,next()
函数存在于 Python 2.6+ 中。但是2.5很容易实现,见next() function implementation for Python 2.5【参考方案15】:
使用 try-except 子句并没有什么“脏”。这是pythonic的方式。 ValueError
将仅由 .index
方法引发,因为它是您拥有的唯一代码!
回答评论:
在 Python 中,easier to ask forgiveness than to get permission 哲学已经确立,no index
不会针对任何其他问题引发此类错误。不是我能想到的。
【讨论】:
当然例外是针对特殊情况的,但事实并非如此。如果异常比 ValueError 更具体,我就不会遇到这样的问题。 我知道它只能从那个 method 抛出,但它是否保证只能从那个 reason 抛出?并不是说我能想到索引失败的另一个原因..但是对于那些你可能没有想到的事情来说,这不是例外吗?.get(index, '')
不是更pythonic吗?更不用说更短更易读了。
当我期望密钥存在时我使用 dict[key],当我不确定时我使用 dict.get(key),我 am 在这里寻找等效的东西.返回 None
而不是 -1 会很好,但是正如您自己评论的那样, str.find() 返回 -1 那么为什么不应该有 list.find() 做同样的事情呢?我不买“pythonic”论点
但关键是最 Pythonic 的解决方案是使用 only try/except 而不是 -1 sentinel 值。 IE。你应该重写otherfunction
。另一方面,如果它没有坏,...【参考方案16】:
我不知道为什么你会认为它很脏……因为例外?如果你想要一个oneliner,这里是:
thing_index = thing_list.index(elem) if thing_list.count(elem) else -1
但我建议不要使用它;我认为 Ross Rogers 的解决方案是最好的,使用一个对象来封装你想要的行为,不要试图以可读性为代价将语言推向极限。
【讨论】:
是的,因为例外。您的代码将执行两个线性搜索,不是吗?在这里,性能并不重要。 SuperDuperList 解决方案很好,但在这种特殊情况下似乎有点矫枉过正。我想我最终会捕捉到异常,但我想看看是否有一种更清洁(符合我的审美)的方式。 @Draemon: 好吧,你将把你拥有的代码封装到find()
函数中,它会很干净;)
奇怪的是,我的回答有两个反对意见,而 Emil Ivanov 的回答虽然在语义上相同,但却是最赞成的回答之一。很可能发生这种情况是因为我的速度较慢,因为我使用 count() 而不是“in”运算符……至少有一条评论说那会很棒,尽管 :-)以上是关于在python中处理list.index(可能不存在)的最佳方法?的主要内容,如果未能解决你的问题,请参考以下文章
Python常见错误:IndexError: list index out of range