Индекс Python элемента в списке без ошибок?

Возможный дубликат:
Вопрос Python list.index

Чтобы найти индекс элемента в списке, вы используете:

list.index(x)
Return the index in the list of the first item whose value is x. 
It is an error if there is no such item.

Мне кажется немного странным, что он выкинет ошибку, если элемент не найден. Откуда я пришел (земля Objective-C), он возвращает перечисление NSNotFound (это всего лишь max int, указывающее, что элемент не найден).

Итак, я сделал что-то уродливое, чтобы обойти это:

index = 0
for item in self.items:
   if item.id == desired_id:
        return index
    index = index + 1
 return -1

Я использовал -1, чтобы указать, что элемент не найден. Какой лучший способ сделать это, и почему у Python нет такого встроенного?

Ответы

Ответ 1

a = [1]
try:
    index_value = a.index(44)
except ValueError:
    index_value = -1

Как насчет этого?

Ответ 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
... 

Первое и второе решения просматривают список только один раз, поэтому вы можете думать, что они быстрее третьего, потому что он дважды просматривает список. Поэтому давайте посмотрим:

>>> 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

Как вы можете видеть, в этом случае третье решение превосходит даже второе, и оба они почти в 4 раза быстрее, чем код python. В зависимости от того, как часто вы ожидаете неудачного поиска, вы хотите выбрать # 2 или # 3 (хотя в 99% случаев номер №2 лучше).

Как правило, если вы хотите оптимизировать что-то для CPython, тогда вы хотите делать столько итераций "на уровне C", сколько сможете. В вашем примере повторение с использованием цикла for - это то, что вы не хотите делать.

Ответ 4

использовать обработку исключений, list.index вызывает ValueError, чтобы вы могли поймать это исключение:

Простой пример:

In [78]: lis=[1,2,3,4]

In [79]: for i in range(-1,6):
    try:
        print lis.index(i)
    except ValueError:    
        print i,"not found"

-1 not found
0 not found
0
1
2
3
5 not found

Ответ 5

Существует явная причина такого поведения:

>>> from import this
...
In the face of ambiguity, refuse the temptation to guess.
...

Нет четкой интерпретации того, как система должна реагировать на такой объект, как "NSNotFound", поэтому вы должны отказаться от угадывания, а затем было бесполезно реализовать для этого специальную функцию.

подумайте, что произойдет, если я попытаюсь сделать что-то вроде этого:

[ objective.index(i)+1 for i in reference_list ]

Что значит добавить 1 к NSNotFound? не проще ли сделать что-то вроде:

[ objective.index(i)+1 for i in reference_list if i in objective ]

И (-1) на самом деле является допустимым индексом для списка, это означает "принять последнее значение", поэтому, если вы попытаетесь использовать его в качестве специального кода ошибки, очень правдоподобно, что вы собираетесь какая-то неприятная, неприятная ошибка.

Guido имеет очень сильное чувство дизайна, не стоит его недооценивать;)

Если, тем не менее, вам все равно нужно что-то подобное, вы можете попробовать с помощью этого кода:

class NotFoundError(Exception):
    def __init__(self,container,index):
        self.message = "object "+str(index)+" not found on "+str(container)
        self.container = container
        self.index = index
    def __str__(self):
        return self.message

def getindex(cont,idx):
    try:
        return cont.index(idx)
    except:
        return NotFoundError(cont,idx)

a = [1,2]

print getindex(a,3)
#object 3 not found on [1, 2]

Ответ 6

Лучше подумать об этом как о "повышении исключения", чем о том, чтобы "выбросить ошибку".

Исключения в Python не только для ошибок, но и для исключительных обстоятельств - отсюда и название. Если list.index() возвратил какое-то особое значение, это должно было бы быть тем, что

  • не мог быть возвращен, если list.index() нашел элемент

  • не может быть впоследствии неверно истолкован наивным кодом.

Первое условие исключает все положительные целые числа (включая нуль и sys.maxint), а второе исключает негативные (также, что отрицательные индексы являются допустимым способом индексирования в список на Python). Любое другое, кроме целого, скорее всего, приведет к возникновению исключения в любом случае, если последующий код предполагает, что он собирается получить.

Независимо от того, вызывает ли метод исключение или возвращает специальное значение, вам часто нужно что-то делать с этой информацией, и это:

try:
    index = list.index(x)
except ValueError:
    # do something

более читаем, чем это:

index = list.index(x)
if index == some_special_value:
    # do something

... и в последнем случае, забыв остерегаться исключительных обстоятельств, может произойти сбой кода, что может привести к запутыванию ошибок в другом месте кода.

Хуже того, вам нужно будет запомнить или посмотреть, что это за особенное значение, для этого и любых других методов или функций, которые ведут себя так.