Лучший способ обрабатывать list.index (может не существовать) в Python?

113

У меня есть код, который выглядит примерно так:

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]

Есть ли более чистый способ добиться того же? Предположим, список не отсортирован.

Draemon
источник
4
"... в этом случае я хочу передать -1 как thing_index." - Это определенно непифонично. Передача (бессмысленного) значения токена в случае, если операция не удалась, не одобряется - исключения действительно здесь правильный путь. Тем более, thing_list[-1]что это допустимое выражение, означающее последнюю запись в списке.
Тим Пицкер
@jellybean: facepalm ... найди java-кодера: P
Draemon
4
@Tim: есть str.findметод, который делает именно это: возвращается, -1когда игла не найдена в теме.
SilentGhost
@Tim None тогда будет лучше ... и это будет аналогично dict [key] vs dict.get [key]
Draemon
@SilentGhost: Хм, интересно. Возможно, мне придется разобраться в этом более подробно. str.index()генерирует исключение, если строка поиска не найдена.
Тим Пицкер

Ответы:

66

В использовании предложения try-except нет ничего «грязного». Это питонический путь. ValueErrorбудет вызван только .indexметодом, потому что это единственный код, который у вас есть!

Чтобы ответить на комментарий:
в Python легче просить прощения, чем получать разрешение, философия хорошо известна, и нет index не вызовет этот тип ошибки для каких-либо других проблем. Не то чтобы я мог думать ни о чем.

SilentGhost
источник
29
Конечно, исключения бывают для исключительных случаев, а это вряд ли. У меня не было бы такой проблемы, если бы исключение было более конкретным, чем ValueError.
Draemon
1
Я знаю, что его можно выбросить только из этого метода, но гарантированно ли оно будет выброшено только по этой причине ? Не то чтобы я мог придумать другую причину, по которой индекс не сработает ... но разве не являются исключениями именно те вещи, о которых вы можете не думать?
Draemon
4
Разве не {}.get(index, '')более питонический? Не говоря уже о том, что короче читабельнее.
Эстебан Кюбер,
1
Я использую Dict [ключ] , когда я ожидаю , что ключ существовать и dict.get (ключ) , когда я не уверен, и я ищу ищу что - то эквивалент здесь. Возврат Noneвместо -1 было бы нормально, но, как вы сами прокомментировали, str.find () возвращает -1, так почему не должно быть list.find (), который делает то же самое? Я не
верю «питоническому
3
Но дело в том, что наиболее питоническим решением является использование только try / except, а не значение -1 дозорного. IE тебе надо переписать otherfunction. С другой стороны, если он не сломан, ...
Эндрю Джаффе
53
thing_index = thing_list.index(elem) if elem in thing_list else -1

Одна линия. Просто. Без исключений.

Эмиль Иванов
источник
35
Просто да, но это приведет к двум линейным поискам, и хотя производительность сама по себе не является проблемой, это кажется чрезмерным.
Draemon
4
@Draemon: Согласитесь - это сделает 2 прохода - но маловероятно, что из тысячи строк кода этот будет узким местом. :) Всегда можно подписаться на императивное решение с помощью for.
Эмиль Иванов
with lambdindexOf = lambda item,list_ : list_.index(item) if item in list_ else -1 # OR None
Alaa
17

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 )
Росс Роджерс
источник
6

Нет ничего плохого в вашем коде, который использует ValueError. Вот еще один однострочник, если вы не хотите исключений:

thing_index = next((i for i, x in enumerate(thing_list) if x == thing), -1)
JFS
источник
Это Python 2.6? Я знаю, что не упоминал об этом, но я использую 2.5. Вероятно, это то, что я сделал бы в
версии
1
@Draemon: Да, next()функция существует в Python 2.6+. Но это легко реализовать для 2.5, см. Реализацию функции next () для Python 2.5
jfs
4

Это вопрос философии языка. В Java, например, всегда существовала традиция, согласно которой исключения действительно должны использоваться только в «исключительных обстоятельствах», то есть когда произошли ошибки, а не для управления потоком . Вначале это было из соображений производительности, поскольку исключения Java выполнялись медленно, но теперь это стало общепринятым стилем.

Напротив, Python всегда использовал исключения для обозначения нормального выполнения программы, например, поднимая a, ValueErrorкак мы здесь обсуждаем. В стиле Python нет ничего «грязного», и есть еще много всего, откуда это взялось. Еще более распространенный пример - StopIterationисключение, которое вызывается методом итератора, next()чтобы сигнализировать об отсутствии других значений.

Тендаи Мавуше
источник
На самом деле, JDK бросает путь слишком много проверенные исключения, так что я не уверен , что философия фактически применяется к Java. У меня нет проблем как таковых, StopIterationпотому что ясно определено, что означает исключение. ValueErrorпросто слишком общий.
Draemon
Я имел в виду идею о том, что исключения не должны использоваться для управления потоком: c2.com/cgi/wiki?DontUseExceptionsForFlowControl , а не столько количество проверенных исключений, которые есть в Java, о которых идет совсем
Тендаи Мавуше
4

Если вы делаете это часто, лучше использовать вспомогательную функцию:

def index_of(val, in_list):
    try:
        return in_list.index(val)
    except ValueError:
        return -1 
Венет Редди
источник
4

А как насчет этого 😃:

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 
Алаа Акиэль
источник
1

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

otherfunction(thing_collection, thing)

Вместо того, чтобы открывать что-то настолько зависящее от реализации, как индекс списка в интерфейсе функции, передайте коллекцию и объект и позвольте другой функции заниматься проблемами «проверки принадлежности». Если другая функция написана как независимая от типа коллекции, она, вероятно, будет начинаться с:

if thing in thing_collection:
    ... proceed with operation on thing

который будет работать, если thing_collection является списком, кортежем, набором или dict.

Возможно, это яснее, чем:

if thing_index != MAGIC_VALUE_INDICATING_NOT_A_MEMBER:

это код, который у вас уже есть в другой функции.

PaulMcG
источник
1

А как насчет этого:

temp_inx = (L + [x]).index(x) 
inx = temp_inx if temp_inx < len(L) else -1
Цзе Сюн
источник
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
>>>
хаави
источник
-2

Я не знаю, почему вы должны думать, что он грязный ... из-за исключения? Если вам нужен одинарный лайнер, вот он:

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

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

Алан Франзони
источник
1
Да, за исключением. Ваш код будет выполнять два линейных поиска, не так ли? Не то чтобы производительность здесь действительно имела значение. Решение SuperDuperList хорошее, но в данной конкретной ситуации кажется излишним. Я думаю, что в конечном итоге просто поймаю исключение, но я хотел посмотреть, есть ли более чистый (с моей эстетики) способ.
Draemon
@Draemon: ну, вы инкапсулируете код, который у вас есть, в find()функцию, и все будет чисто;)
SilentGhost
1
Любопытно, что у моего ответа два отрицательных голоса, а у Эмиля Иванова, хотя семантически идентичный, один из самых высоких. Скорее всего, это происходит потому, что мой работает медленнее, поскольку я использовал count () вместо оператора in ... по крайней мере, комментарий, в котором говорится, что это было бы здорово :-)
Алан Франзони
-2

Я бы предложил:

if thing in thing_list:
  list_index = -1
else:
  list_index = thing_list.index(thing)
Йонас
источник
2
Проблема с этим решением заключается в том, что «-1» является допустимым индексом в списке (последний индекс; первый с конца). Лучший способ справиться с этим - вернуть False в первой ветви вашего условия.
FanaticD