Najlepszy sposób na obsługę list.index (może nie istnieć) w Pythonie?

113

Mam kod, który wygląda mniej więcej tak:

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

ok, więc to jest uproszczone, ale masz pomysł. Teraz thingmoże nie być na liście, w takim przypadku chcę przekazać -1 jako thing_index. W innych językach tego można się spodziewać, index()gdyby nie mógł znaleźć elementu. W rzeczywistości rzuca ValueError.

Mógłbym to zrobić:

try:
    thing_index = thing_list.index(thing)
except ValueError:
    thing_index = -1
otherfunction(thing_list, thing_index)

Ale to wydaje się brudne, a ponadto nie wiem, czy ValueErrormożna by go podnieść z innego powodu. Wymyśliłem następujące rozwiązanie oparte na funkcjach generatora, ale wydaje się ono nieco skomplikowane:

thing_index = ( [(i for i in xrange(len(thing_list)) if thing_list[i]==thing)] or [-1] )[0]

Czy istnieje czystszy sposób na osiągnięcie tego samego? Załóżmy, że lista nie jest posortowana.

Draemon
źródło
4
„... w takim przypadku chcę przekazać -1 jako thing_index”. - To jest zdecydowanie nie-Pythonic. Przekazywanie (bezsensownej) wartości tokenu w przypadku, gdy operacja się nie powiedzie, jest mile widziane - wyjątki są tutaj naprawdę właściwą drogą. Zwłaszcza, że thing_list[-1]jest to prawidłowe wyrażenie, czyli ostatni wpis na liście.
Tim Pietzcker
@jellybean: facepalm ... znajdź kodera java: P
Draemon
4
@Tim: istnieje str.findmetoda, która dokładnie to robi: zwraca, -1gdy igła nie została znaleziona w temacie.
SilentGhost
@Tim None byłoby wtedy lepsze ... i byłoby to analogiczne do dict [key] vs dict.get [key]
Draemon
@SilentGhost: Hm, ciekawe. Być może będę musiał przyjrzeć się temu bardziej szczegółowo. str.index()zgłasza wyjątek, jeśli szukany ciąg nie zostanie znaleziony.
Tim Pietzcker

Odpowiedzi:

66

Nie ma nic "brudnego" w używaniu klauzuli try-except. To jest sposób pytoniczny. ValueErrorzostanie podniesiony tylko przez .indexmetodę, ponieważ jest to jedyny kod, który tam masz!

Odpowiadając na komentarz:
W Pythonie łatwiej prosić o przebaczenie niż o pozwolenie filozofia jest dobrze ugruntowana i nie index spowoduje to powstania tego typu błędu w innych kwestiach. Nie to, żebym wymyślił jakiekolwiek.

SilentGhost
źródło
29
Z pewnością wyjątki dotyczą wyjątkowych przypadków, a to nie jest to. Nie miałbym takiego problemu, gdyby wyjątek był bardziej szczegółowy niż ValueError.
Draemon
1
Wiem, że można go wyrzucić tylko z tej metody, ale czy gwarantuje się, że zostanie wyrzucony tylko z tego powodu ? Nie to, że przychodzi mi do głowy inny powód, dla którego indeks by się nie udał, ale czy nie są wyjątkami dokładnie dla tych rzeczy, o których możesz nie myśleć?
Draemon,
4
Czy nie jest {}.get(index, '')bardziej pytoniczne? Nie wspominając o krótszym, bardziej czytelnym.
Esteban Küber,
1
Używam dict [key] kiedy spodziewać się kluczem istnieć i dict.get (key), kiedy nie jestem pewien, a ja jestem szukasz czegoś odpowiednik tutaj. Zwrócenie Nonezamiast -1 byłoby w porządku, ale jak sam skomentowałeś, str.find () zwraca -1, więc dlaczego nie miałaby istnieć list.find (), która robi to samo? Nie kupuję argumentu „
pytonicznego
3
Chodzi jednak o to, że najbardziej pythonowym rozwiązaniem jest użycie tylko try / except, a nie wartości wartownika -1 w ogóle. IE powinieneś przepisać otherfunction. Z drugiej strony, jeśli się nie zepsuł, ...
Andrew Jaffe
53
thing_index = thing_list.index(elem) if elem in thing_list else -1

Jedna linia. Prosty. Bez wyjątków.

Emil Ivanov
źródło
35
Proste tak, ale spowoduje to dwa liniowe wyszukiwania i chociaż wydajność nie jest sama w sobie problemem, wydaje się to nadmierne.
Draemon
4
@Draemon: Zgadzam się - to zrobi 2 przebiegi - ale jest mało prawdopodobne, że z bazy kodu tysiąca linii ten będzie wąskim gardłem. :) Zawsze można zdecydować się na imperatywne rozwiązanie z for.
Emil Ivanov
z lambdemindexOf = lambda item,list_ : list_.index(item) if item in list_ else -1 # OR None
Alaa Akiel
17

Plik dictTyp posiada getfunkcję , gdzie jeśli klucz nie istnieje w słowniku, 2nd argument getjest wartością, którą należy zwrócić. Podobnie jest setdefault, który zwraca wartość w, dictjeśli klucz istnieje, w przeciwnym razie ustawia wartość zgodnie z domyślnym parametrem, a następnie zwraca parametr domyślny.

Możesz rozszerzyć listtyp, aby mieć getindexdefaultmetodę.

class SuperDuperList(list):
    def getindexdefault(self, elem, default):
        try:
            thing_index = self.index(elem)
            return thing_index
        except ValueError:
            return default

Które można następnie wykorzystać na przykład:

mylist = SuperDuperList([0,1,2])
index = mylist.getindexdefault( 'asdf', -1 )
Ross Rogers
źródło
6

Nie ma nic złego w Twoim kodzie, który używa ValueError. Oto kolejna jedna linijka, jeśli chcesz uniknąć wyjątków:

thing_index = next((i for i, x in enumerate(thing_list) if x == thing), -1)
jfs
źródło
Czy to Python 2.6? Wiem, że o tym nie wspomniałem, ale używam 2.5. To jest prawdopodobnie to, co zrobiłbym w 2.6
Draemon
1
@Draemon: Tak, next()funkcja istnieje w Pythonie 2.6+. Ale jest łatwy do zaimplementowania dla 2.5, zobacz implementację funkcji next () dla Pythona 2.5
jfs
4

Ta kwestia dotyczy filozofii języka. Na przykład w Javie zawsze istniała tradycja, że ​​wyjątki powinny być używane tylko w „wyjątkowych okolicznościach”, czyli wtedy, gdy wystąpiły błędy, a nie do sterowania przepływem . Na początku było to spowodowane wydajnością, ponieważ wyjątki Java były powolne, ale teraz stało się to akceptowanym stylem.

W przeciwieństwie do tego Python zawsze stosował wyjątki, aby wskazać normalny przepływ programu, na przykład podnosząc a, ValueErroro czym tutaj mówimy. Nie ma w tym nic "brudnego" w stylu Pythona, a jest ich o wiele więcej. Jeszcze bardziej powszechnym przykładem jest StopIterationwyjątek, który jest zgłaszany przez metodę iteratora, next()aby zasygnalizować, że nie ma dalszych wartości.

Tendayi Mawushe
źródło
Faktycznie, JDK rzuca sposób zbyt wiele sprawdzonych wyjątków, więc nie jestem pewien, że filozofia jest rzeczywiście stosowana do Javy. Nie mam problemu per se, StopIterationponieważ jest jasno określone, co oznacza wyjątek. ValueErrorjest trochę zbyt ogólne.
Draemon
Odniosłem się do pomysłu, że wyjątki nie powinny być używane do kontroli przepływu: c2.com/cgi/wiki?DontUseExceptionsForFlowControl , nie tyle liczba sprawdzonych wyjątków, które ma Java, a cała inna dyskusja: mindview.net/Etc/Discussions / CheckedExceptions
Tendayi Mawushe
4

Jeśli robisz to często, lepiej wypalić go w funkcji pomocniczej:

def index_of(val, in_list):
    try:
        return in_list.index(val)
    except ValueError:
        return -1 
Veneet Reddy
źródło
4

A co z tym 😃:

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 
Alaa Akiel
źródło
1

A co z tym:

otherfunction(thing_collection, thing)

Zamiast ujawniać coś tak zależnego od implementacji, jak indeks listy w interfejsie funkcji, przekaż kolekcję i coś i pozwól innej funkcji zająć się kwestiami „testu członkostwa”. Jeśli inna funkcja jest napisana jako niezależna od typu kolekcji, prawdopodobnie zaczęłaby się od:

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

co zadziała, jeśli thing_collection jest listą, krotką, zestawem lub dyktowaniem.

Jest to prawdopodobnie jaśniejsze niż:

if thing_index != MAGIC_VALUE_INDICATING_NOT_A_MEMBER:

czyli kod, który już masz w innej funkcji.

PaulMcG
źródło
1

A co z tym:

temp_inx = (L + [x]).index(x) 
inx = temp_inx if temp_inx < len(L) else -1
Jie Xiong
źródło
0

Mam ten sam problem z metodą „.index ()” na listach. Nie mam problemu z tym, że zgłasza wyjątek, ale zdecydowanie nie zgadzam się z faktem, że jest to nieopisowy ValueError. Zrozumiałbym, gdyby był to błąd IndexError.

Rozumiem, dlaczego zwrócenie „-1” również byłoby problemem, ponieważ jest to prawidłowy indeks w Pythonie. Ale realistycznie, nigdy nie oczekuję, że metoda „.index ()” zwróci liczbę ujemną.

Tutaj przechodzi jeden wiersz (ok, to dość długa linia ...), dokładnie raz przechodzi przez listę i zwraca „Brak”, jeśli element nie zostanie znaleziony. Przepisanie go na wartość -1 byłoby trywialne, gdybyś sobie tego życzył.

indexOf = lambda list, thing: \
            reduce(lambda acc, (idx, elem): \
                   idx if (acc is None) and elem == thing else acc, list, None)

Jak używać:

>>> indexOf([1,2,3], 4)
>>>
>>> indexOf([1,2,3], 1)
0
>>>
haavee
źródło
-2

Nie wiem, dlaczego uważasz, że jest brudny ... z powodu wyjątku? jeśli chcesz oneliner, oto jest:

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

ale odradzałbym go używać; Myślę, że rozwiązanie Rossa Rogersa jest najlepsze, użyj obiektu do hermetyzacji swojego zamierzonego zachowania, nie próbuj przesuwać języka do jego granic kosztem czytelności.

Alan Franzoni
źródło
1
Tak, z powodu wyjątku. Twój kod wykona dwa liniowe wyszukiwania, prawda? Nie chodzi o to, że wydajność ma tutaj znaczenie. Rozwiązanie SuperDuperList jest fajne, ale w tej konkretnej sytuacji wydaje się przesadą. Myślę, że w końcu złapię wyjątek, ale chciałem sprawdzić, czy istnieje czystszy (według mojej estetyki) sposób.
Draemon,
@Draemon: cóż, umieścisz kod, który masz w find()funkcji i będzie on czysty;)
SilentGhost
1
Ciekawe, że moja odpowiedź ma dwa głosy przeciw, podczas gdy Emil Iwanow, choć semantycznie identyczny, jest jednym z najbardziej pozytywnych. Najprawdopodobniej dzieje się tak, ponieważ mój jest wolniejszy, ponieważ użyłem count () zamiast operatora "in" ... przynajmniej komentarz mówiący, że byłby świetny :-)
Alan Franzoni
-2

Proponuję:

if thing in thing_list:
  list_index = -1
else:
  list_index = thing_list.index(thing)
Jonas
źródło
2
Problem z tym rozwiązaniem polega na tym, że „-1” jest prawidłowym indeksem do listy (ostatni indeks; pierwszy od końca). Lepszym sposobem radzenia sobie z tym byłoby zwrócenie Fałsz w pierwszej gałęzi twojego stanu.
FanaticD