指摘された一般的な解決策には同意しますが、回答とコメントで説明されたアプローチをもう少し調べて、どちらがより効率的で、どの状況であるかを確認したいと思います。
まず、3つの基本的なアプローチ:
>>> 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
...
1番目と2番目のソリューションはリストを1回だけスキャンするため、リストを2回スキャンするため、3番目のソリューションよりも高速であると考えるかもしれません。だから見てみましょう:
>>> 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
2つ目は実際に最速ですが、リストを1回だけスキャンしたとしても、1つ目は3つ目よりもはるかに遅いことに気付くでしょう。リストのサイズを大きくしても、状況はあまり変わりません。
>>> 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
この場合にわかるように、3番目のソリューションは2番目のソリューションよりも優れており、どちらもPythonコードよりもほぼ4倍高速です。検索が失敗する頻度に応じて、#2または#3を選択します(99%の場合、番号#2の方が適しています)。
原則として、CPython用に何かを最適化する場合は、「Cレベルで」できるだけ多くの反復を実行する必要があります。あなたの例では、forループを使用して反復することは、まさにあなたがしたくないことです。