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

时间:2010-01-25 13:59:37

标签: python list find indexing

我的代码看起来像这样:

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]

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

14 个答案:

答案 0 :(得分:56)

使用try-except子句没有“脏”。这是pythonic的方式。仅ValueError方法会引发.index,因为它是您在那里唯一的代码!

回答评论:
在Python中,easier to ask forgiveness than to get permission哲学已经建立, index不会因任何其他问题引发此类错误。不是我能想到的。

答案 1 :(得分:41)

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

一行。简单。没有例外。

答案 2 :(得分:15)

dict type有一个get function,如果字典中不存在该键,则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 )

答案 3 :(得分:5)

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

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

答案 4 :(得分:3)

这个问题是语言哲学之一。例如,在Java中,一直存在一种传统,即异常应该仅用于发生错误的“异常情况”,而不是flow control。起初这是出于性能原因,因为Java异常很慢,但现在这已成为公认的风格。

相比之下,Python总是使用异常来指示正常的程序流,就像我们在这里讨论的那样引发ValueError。在Python风格中没有任何“脏”的东西,还有更多来自它的地方。一个更常见的例子是StopIteration exception,它由迭代器的next()方法引发,表示没有其他值。

答案 5 :(得分: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:

这是你在其他功能中已有的代码。

答案 6 :(得分:1)

怎么这样:

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

答案 7 :(得分: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("")

当作为主程序运行时,这会在我的机器上产生以下打印输出,以秒为单位指示完成每个函数 1000 次试验的时间:

方法一: 0.7502102799990098

方法二: 0.7291318440002215

方法三: 0.24142152300009911

方法四: 0.5253471979995084

方法五,numpy给定列表: 0.5045417560013448

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

当然,该问题专门询问列表,因此最好的解决方案是使用 try-except 方法,但是通过使用 numpy 数据结构和运算符而不是python 数据结构很重要,如果在许多对性能至关重要的数据数组上构建某些东西,那么作者应该尝试始终使用 numpy 来利用超快的 C 绑定。 (CPython 解释器,其他解释器性能可能有所不同)

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

答案 8 :(得分:0)

我对列表中的“.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
>>>

答案 9 :(得分:0)

如果你经常这样做,那么最好把它放在辅助功能中去除:

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

答案 10 :(得分:0)

那?:

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 

答案 11 :(得分: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

时序案例 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 - 除非出现两种情况。然后 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 个时,这种开销变得可以忽略不计(在我的机器上)。

答案 12 :(得分:-2)

我不知道你为什么认为它很脏...因为例外?如果你想要一个oneliner,这里是:

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

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

答案 13 :(得分:-2)

我建议:

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