我同意所指出的一般解决方案,但我想更多地研究答案和评论中解释的方法,看看哪种方法更有效以及在哪些情况下。
首先,三种基本方法:
>>> def my_index(L, obj):
... for i, el in enumerate(L):
... if el == obj:
... return i
... return -1
...
>>> def my_index2(L, obj):
... try:
... return L.index(obj)
... except ValueError:
... return -1
...
>>> def my_index3(L, obj):
... if obj in L:
... return L.index(obj)
... return -1
...
第一个和第二个解决方案只扫描列表一次,因此您可能认为它们比第三个更快,因为它扫描列表两次。那么让我们看看:
>>> timeit.timeit('my_index(L, 24999)', 'from __main__ import my_index, L', number=1000)
1.6892211437225342
>>> timeit.timeit('my_index2(L, 24999)', 'from __main__ import my_index2, L', number=1000)
0.403195858001709
>>> timeit.timeit('my_index3(L, 24999)', 'from __main__ import my_index3, L', number=1000)
0.7741198539733887
好吧,第二个确实是最快的,但是您可以注意到第一个比第三个慢得多,即使它只扫描列表一次。如果我们增加列表的大小,事情并没有太大变化:
>>> L = list(range(2500000))
>>> timeit.timeit('my_index(L, 2499999)', 'from __main__ import my_index, L', number=100)
17.323430061340332
>>> timeit.timeit('my_index2(L, 2499999)', 'from __main__ import my_index2, L', number=100)
4.213982820510864
>>> timeit.timeit('my_index3(L, 2499999)', 'from __main__ import my_index3, L', number=100)
8.406487941741943
第一个仍然慢 2 倍。
如果我们搜索不在列表中的内容,第一个解决方案的情况会变得更糟:
>>> timeit.timeit('my_index(L, None)', 'from __main__ import my_index, L', number=100)
19.055058002471924
>>> timeit.timeit('my_index2(L, None)', 'from __main__ import my_index2, L', number=100)
5.785136938095093
>>> timeit.timeit('my_index3(L, None)', 'from __main__ import my_index3, L', number=100)
5.46164608001709
正如您在这种情况下所看到的,第三种解决方案甚至胜过第二种解决方案,并且两者都比 python 代码快了近 4 倍。根据您期望搜索失败的频率,您希望选择#2 或#3(即使在 99% 的情况下,#2 更好)。
作为一般规则,如果您想为 CPython 优化某些东西,那么您希望尽可能多地“在 C 级别”进行迭代。在您的示例中,使用 for 循环进行迭代正是您不想做的事情。