Найкращий спосіб обробити list.index (можливо, не існує) в python?


113

У мене є код, який виглядає приблизно так:

thing_index = thing_list.index(thing)
otherfunction(thing_list, thing_index)

Ок, це спрощено, але ви розумієте. Зараз thingнасправді у списку може не бути, і в цьому випадку я хочу передати -1 як thing_index. В інших мовах це очікування index()повернення, якщо ви не зможете знайти елемент. Фактично це кидає a 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]

Чи є більш чистий спосіб досягти того самого? Припустимо, що список не відсортований.


4
"... в такому випадку я хочу передати -1 як thing_index." - Це, безумовно, непітонічно. Передача (безглуздого) значення лексеми у випадку, якщо операція не вдається, нахмуриться - винятки справді є правильним способом. Тим більше thing_list[-1], що це дійсний вираз, що означає останній запис у списку.
Тім Піцкер

@jellybean: facepalm ... знайдіть код java: P
Draemon

4
@Tim: існує str.findметод, який робить саме це: повертається, -1коли голку не знайдено в темі.
SilentGhost

@Tim Ніхто не був би кращим тоді ... і це було б аналогічно dict [key] vs dict.get [key]
Draemon

@SilentGhost: Хм, цікаво. Мені, можливо, доведеться розглянути це більш детально. str.index()викидає виняток, якщо рядок пошуку не знайдено.
Тім Піцкер

Відповіді:


65

Немає нічого "брудного" у використанні випробування, крім пункту. Це пітонічний шлях. ValueErrorбуде піднято .indexлише методом, тому що це єдиний код, який у вас є!

Щоб відповісти на коментар:
У Python простіше попросити пробачення, ніж отримати філософію дозволу , добре встановлена, і жодна index проблема не призведе до подібних помилок. Не те, що я можу придумати будь-яке.


29
Безумовно, винятки є для виняткових випадків, і це навряд чи це. У мене не було б такої проблеми, якби виняток був більш конкретним, ніж ValueError.
Драмон

1
Я знаю, що це може бути викинуто лише з цього методу, але чи гарантовано його викинути лише з цієї причини ? Не те, що я можу придумати іншу причину індексу, але не є винятками саме для тих речей, про які ви можете не думати?
Draemon

4
Хіба не {}.get(index, '')більше пітонічного? Не кажучи вже про коротші більш читабельні.
Естебан Кюбер

1
Я використовую Dict [ключ] , коли я очікую , що ключ існувати і dict.get (ключ) , коли я не впевнений, і я шукаю шукаю що - то еквівалент тут. Повернення Noneзамість -1 було б добре, але як ви самі прокоментували, str.find () повертає -1, то чому б не було list.find (), що робить те саме? Я не купую "пітонічний" аргумент
Draemon

3
Але справа в тому, що найбільш пітонічним рішенням є використання лише спроб / за винятком, а не значення дозорного взагалі. IE вам слід переписати otherfunction. З іншого боку, якщо це не зламається, ...
Ендрю Яффе

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

Один рядок. Простий. Немає винятків.


35
Просте так, але це дозволить виконати два лінійні пошуки, і хоча продуктивність не є проблемою, це здається надмірним.
Draemon

4
@Draemon: Погодьтеся - це зробить два проходи - але навряд чи з тисячолінійної кодової бази це буде вузьким місцем. :) Завжди можна погодитися на необхідне вирішення проблеми for.
Еміль Іванов

with lambdindexOf = lambda item,list_ : list_.index(item) if item in list_ else -1 # OR None
Alaa Akiel

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)

Це пітон 2.6? Я знаю, що про це не згадував, але використовую 2.5. Це, мабуть, те, що я робив би в 2.6
Draemon

1
@Draemon: Так, next()функція існує в Python 2.6+. Але це легко реалізувати для 2.5, дивіться наступну реалізацію функції для Python 2.5
jfs

4

Це питання є філософією мови. Наприклад, у Java завжди існувала традиція, що винятки повинні використовуватися лише у "виняткових обставинах", тобто коли трапляються помилки, а не для контролю потоку . Спочатку це було з міркувань продуктивності, оскільки винятки Java були повільними, але тепер це стало загальноприйнятим стилем.

На відміну від цього, Python завжди використовував винятки, щоб вказати на нормальний потік програми, як, наприклад, підвищення a, ValueErrorяк ми обговорюємо тут. У цьому немає нічого "брудного" в стилі Python, і є багато інших, звідки це взялося. Ще більш поширеним прикладом є StopIterationвиняток, який піднімається методом ітератора next()для сигналізації про відсутність подальших значень.


На насправді, JDK кидає шлях надто багато перевірені виключення, так що я не впевнений , що філософія фактично застосовується до Java. У мене немає проблем із StopIterationсамим собою, оскільки чітко визначено, що означає виняток. ValueErrorпросто трохи загальний.
Draemon

Я мав на увазі ідею, що виключення не слід використовувати для контролю потоку: c2.com/cgi/wiki?DontUseExceptionsForFlowControl , не стільки кількість перевірених винятків, що у Java, і ціла інша дискусія: mindview.net/Etc/Discussions / Перевірені винятки
Tendayi Mawushe

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 - це список, кортеж, набір чи діктант.

Це, можливо, зрозуміліше, ніж:

if thing_index != MAGIC_VALUE_INDICATING_NOT_A_MEMBER:

який код у вас вже є в іншій функції.



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

Я не знаю, чому ви повинні вважати, що це брудно ... через виняток? якщо ви хочете oneliner, ось це:

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.