[英]Best way to handle list.index(might-not-exist) in python?
我有看起来像这样的代码:
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]
有没有更干净的方法来实现同样的目标? 让我们假设列表没有排序。
使用 try-except 子句没有任何“肮脏”之处。 这是pythonic的方式。 ValueError
只会由.index
方法引发,因为它是您在那里唯一的代码!
要回答评论:
在 Python 中,请求宽恕比获得许可更容易的哲学已经确立,并且没有index
不会为任何其他问题引发此类错误。 不是我能想到的。
thing_index = thing_list.index(elem) if elem in thing_list else -1
一条线。 简单的。 没有例外。
dict
类型有一个get
函数,如果字典中不存在键,则get
的第二个参数是它应该返回的值。 同样有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 )
如果您经常这样做,那么最好将其存放在辅助函数中:
def index_of(val, in_list):
try:
return in_list.index(val)
except ValueError:
return -1
您使用ValueError
的代码没有任何问题。 如果您想避免例外,这里还有一个单行:
thing_index = next((i for i, x in enumerate(thing_list) if x == thing), -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
这个问题是语言哲学问题之一。 例如,在 Java 中一直有一个传统,即异常应该只在“异常情况”中使用,即发生错误时,而不是用于流量控制。 一开始这是出于性能原因,因为 Java 异常很慢,但现在这已成为公认的风格。
相比之下,Python 一直使用异常来表示正常的程序流程,比如我们在这里讨论的引发ValueError
。 在 Python 风格中,这并没有什么“肮脏”的地方,而且还有更多的地方。 一个更常见的例子是StopIteration
异常,它由迭代器的next()
方法引发,以表示没有其他值。
那这个呢:
otherfunction(thing_collection, thing)
与其在函数接口中公开诸如列表索引之类的依赖于实现的东西,不如传递集合和事物并让其他函数处理“成员资格测试”问题。 如果 otherfunction 被编写为与集合类型无关,那么它可能会以:
if thing in thing_collection:
... proceed with operation on thing
如果 thing_collection 是列表、元组、集合或字典,这将起作用。
这可能比:
if thing_index != MAGIC_VALUE_INDICATING_NOT_A_MEMBER:
这是您在其他功能中已有的代码。
像这样怎么样:
temp_inx = (L + [x]).index(x)
inx = temp_inx if temp_inx < len(L) else -1
已经有一段时间了,但它是 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.7502102799990098
方法二:0.7291318440002215
方法三:0.24142152300009911
方法四:0.5253471979995084
方法五、numpy给定列表:0.5045417560013448
方法六,numpy给定np.ndarray:0.011147511999297421
当然,这个问题专门询问列表,因此最好的解决方案是使用 try-except 方法,但是通过使用 numpy 数据结构和运算符而不是 python 数据结构提供了速度改进(与 try-except 相比,这里至少提高了 20 倍)很重要,如果在许多对性能至关重要的数据数组上构建某些东西,那么作者应该尝试始终使用 numpy 来利用超快的 C 绑定。 (CPython 解释器,其他解释器性能可能有所不同)
顺便说一句,方法 5 比方法 6 慢得多的原因是因为 numpy 首先必须将给定的列表转换为它自己的 numpy 数组,所以给它一个列表并不会破坏它,它只是没有充分利用可能的速度。
我对列表中的“.index()”方法有同样的问题。 我对它抛出异常这一事实没有异议,但我强烈不同意它是一个非描述性的 ValueError 这一事实。 不过,我可以理解它是否是一个 IndexError。
我可以理解为什么返回“-1”也是一个问题,因为它是 Python 中的有效索引。 但实际上,我从不期望“.index()”方法返回负数。
这里有一个单行(好吧,这是一个相当长的行......),只遍历列表一次,如果未找到该项目,则返回“无”。 如果您愿意,重写它以返回 -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
>>>
Python 3.8 上的简单比较
TL;DR maybeidx2
通常更快,除了有很多未命中的数组 (n<100)
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
%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)
%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 个元素时,这种开销变得可以忽略不计(在我的机器上)。
我会直言不讳:这里的答案非常糟糕,并且具有疯狂的时间复杂度。
这里有一个简单的方法。
使用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
我建议:
if thing in thing_list:
list_index = -1
else:
list_index = thing_list.index(thing)
我不知道为什么你应该认为它很脏......因为例外? 如果你想要一个oneliner,这里是:
thing_index = thing_list.index(elem) if thing_list.count(elem) else -1
但我建议不要使用它; 我认为 Ross Rogers 的解决方案是最好的,使用一个对象来封装你想要的行为,不要试图以可读性为代价将语言推向极限。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.