Il modo migliore per gestire list.index (potrebbe-non-esistere) in Python?


113

Ho un codice che assomiglia a questo:

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

ok quindi è semplificato ma hai capito. Ora thingpotrebbe non essere effettivamente nell'elenco, nel qual caso voglio passare -1 come thing_index. In altre lingue questo è ciò che ti aspetteresti index()di restituire se non riuscisse a trovare l'elemento. In effetti lancia un file ValueError.

Potrei farlo:

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

Ma questo sembra sporco, inoltre non so se ValueErrorpotrebbe essere cresciuto per qualche altro motivo. Ho trovato la seguente soluzione basata sulle funzioni del generatore, ma sembra un po 'complessa:

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

Esiste un modo più pulito per ottenere la stessa cosa? Supponiamo che l'elenco non sia ordinato.


4
"... nel qual caso voglio passare -1 come thing_index." - Questo è decisamente anti-pitonico. Il passaggio di un valore di token (privo di significato) nel caso in cui un'operazione non abbia successo è disapprovato - le eccezioni sono davvero la strada giusta qui. Soprattutto perché thing_list[-1]è un'espressione valida, ovvero l'ultima voce nell'elenco.
Tim Pietzcker

@jellybean: facepalm ... individua il programmatore java: P
Draemon

4
@Tim: esiste un str.findmetodo che fa esattamente questo: restituisce -1quando l'ago non viene trovato nell'oggetto.
SilentGhost

@Tim None sarebbe meglio allora ... e questo sarebbe analogo a dict [key] vs dict.get [key]
Draemon

@ SilentGhost: Hm, interessante. Potrei dover esaminare questo aspetto in modo più dettagliato. str.index()genera un'eccezione se la stringa di ricerca non viene trovata.
Tim Pietzcker

Risposte:


66

Non c'è niente di "sporco" nell'uso della clausola try-tranne. Questo è il modo pitonico. ValueErrorverrà generato solo dal .indexmetodo, perché è l'unico codice che hai lì!

Per rispondere al commento:
In Python, è più facile chiedere perdono che ottenere i permessi la filosofia è ben consolidata e no index non solleverà questo tipo di errore per nessun altro problema. Non che io riesca a pensare a nessuno.


29
Sicuramente le eccezioni sono per casi eccezionali, e questo non è certo questo. Non avrei un problema del genere se l'eccezione fosse più specifica di ValueError.
Draemon

1
So che può essere lanciato solo da quel metodo, ma è garantito che venga lanciato solo per quel motivo ? Non che riesca a pensare a un altro motivo per cui l'indice fallirebbe ... ma allora non sono eccezioni per esattamente quelle cose a cui potresti non pensare?
Draemon

4
Non è {}.get(index, '')più pitonico? Per non parlare di più breve più leggibile.
Esteban Küber

1
Uso dict [key] quando mi aspetto che la chiave esista e dict.get (key) quando non sono sicuro, e sto cercando qualcosa di equivalente qui. Restituire Noneinvece di -1 andrebbe bene, ma come hai commentato tu stesso, str.find () restituisce -1 quindi perché non dovrebbe esserci list.find () che fa la stessa cosa? Non sto comprando l'argomento "pitonico"
Draemon

3
Ma il punto è che la soluzione più pitonica è usare solo try / tranne e non il valore sentinella -1. Vale a dire che dovresti riscrivere otherfunction. D'altra parte, se non è rotto, ...
Andrew Jaffe

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

Una linea. Semplice. Nessuna eccezione.


35
Semplice sì, ma questo farà due ricerche lineari e sebbene le prestazioni non siano un problema di per sé, sembra eccessivo.
Draemon

4
@Draemon: D'accordo - che farà 2 passaggi - ma è improbabile che da una base di codice di mille righe questo sia il collo di bottiglia. :) Si può sempre optare per una soluzione imperativa con for.
Emil Ivanov

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

17

Il dicttipo ha una getfunzione , dove se la chiave non esiste nel dizionario, il secondo argomento di getè il valore che dovrebbe restituire. Allo stesso modo c'è setdefault, che restituisce il valore nel dictse la chiave esiste, altrimenti imposta il valore in base al parametro predefinito e quindi restituisce il parametro predefinito.

Potresti estendere il listtipo per avere un getindexdefaultmetodo.

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

Che potrebbe quindi essere utilizzato come:

mylist = SuperDuperList([0,1,2])
index = mylist.getindexdefault( 'asdf', -1 )

6

Non c'è niente di sbagliato nel codice che usa ValueError. Ecco un'altra battuta se vuoi evitare eccezioni:

thing_index = next((i for i, x in enumerate(thing_list) if x == thing), -1)

Quello è Python 2.6? So di non averlo menzionato, ma sto usando 2.5. Questo è probabilmente quello che farei in 2.6
Draemon

1
@Draemon: Sì, la next()funzione esiste in Python 2.6+. Ma è facile da implementare per 2.5, vedere l'implementazione della funzione next () per Python 2.5
jfs

4

Questo problema riguarda la filosofia del linguaggio. In Java, ad esempio, c'è sempre stata una tradizione secondo cui le eccezioni dovrebbero essere utilizzate solo in "circostanze eccezionali", cioè quando si sono verificati errori, piuttosto che per il controllo del flusso . All'inizio questo era per motivi di prestazioni poiché le eccezioni Java erano lente, ma ora questo è diventato lo stile accettato.

Al contrario, Python ha sempre utilizzato le eccezioni per indicare il normale flusso del programma, come sollevare a ValueErrorcome stiamo discutendo qui. Non c'è niente di "sporco" in questo in stile Python e ce ne sono molti altri da dove provengono. Un esempio ancora più comune è l' StopIterationeccezione che viene sollevata dal next()metodo di un iteratore per segnalare che non ci sono ulteriori valori.


In realtà, il JDK getta via troppe eccezioni controllate, quindi non sono sicuro che la filosofia sia effettivamente applicata a Java. Non ho problemi di per sé StopIterationperché è chiaramente definito il significato dell'eccezione. ValueErrorè solo un po 'troppo generico.
Draemon

Mi riferivo all'idea che le eccezioni non dovrebbero essere utilizzate per il controllo del flusso: c2.com/cgi/wiki?DontUseExceptionsForFlowControl , non tanto il numero di eccezioni verificate che Java ha su tutta un'altra discussione: mindview.net/Etc/Discussions / CheckedExceptions
Tendayi Mawushe

4

Se lo fai spesso, è meglio spegnerlo in una funzione di aiuto:

def index_of(val, in_list):
    try:
        return in_list.index(val)
    except ValueError:
        return -1 

4

Che ne dici di questo 😃:

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

Che dire di questo:

otherfunction(thing_collection, thing)

Piuttosto che esporre qualcosa di così dipendente dall'implementazione come un indice di lista in un'interfaccia di funzione, passare la raccolta e la cosa e lasciare che altre funzioni si occupino dei problemi di "verifica dell'appartenenza". Se l'altra funzione è scritta per essere indipendente dal tipo di raccolta, probabilmente inizierebbe con:

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

che funzionerà se thing_collection è una lista, una tupla, un set o un dict.

Questo è forse più chiaro di:

if thing_index != MAGIC_VALUE_INDICATING_NOT_A_MEMBER:

che è il codice che hai già in un'altra funzione.


1

Che ne dici di questo:

temp_inx = (L + [x]).index(x) 
inx = temp_inx if temp_inx < len(L) else -1

0

Ho lo stesso problema con il metodo ".index ()" negli elenchi. Non ho alcun problema con il fatto che generi un'eccezione, ma sono fortemente in disaccordo con il fatto che si tratti di un ValueError non descrittivo. Potrei capire se sarebbe stato un IndexError, però.

Posso capire perché anche la restituzione di "-1" sarebbe un problema perché è un indice valido in Python. Ma realisticamente, non mi aspetto mai che un metodo ".index ()" restituisca un numero negativo.

Qui va una riga (ok, è una riga piuttosto lunga ...), scorre l'elenco esattamente una volta e restituisce "Nessuno" se l'elemento non viene trovato. Sarebbe banale riscriverlo per restituire -1, se lo desideri.

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

Come usare:

>>> indexOf([1,2,3], 4)
>>>
>>> indexOf([1,2,3], 1)
0
>>>

-2

Non so perché dovresti pensare che sia sporco ... a causa dell'eccezione? se vuoi un oneliner, eccolo:

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

ma sconsiglierei di usarlo; Penso che la soluzione di Ross Rogers sia la migliore, usa un oggetto per incapsulare il tuo comportamento desiderato, non provare a spingere il linguaggio ai suoi limiti a scapito della leggibilità.


1
Sì, a causa dell'eccezione. Il tuo codice eseguirà due ricerche lineari, vero? Non che le prestazioni siano davvero importanti qui. La soluzione SuperDuperList è carina, ma sembra eccessiva in questa particolare situazione. Penso che finirò per cogliere l'eccezione, ma volevo vedere se c'era un modo più pulito (per la mia estetica).
Draemon

@Draemon: beh, incapsulerai il codice che hai nella find()funzione e sarà tutto pulito;)
SilentGhost

1
È curioso che la mia risposta abbia due voti negativi, mentre quello di Emil Ivanov, sebbene semanticamente identico, è quello più votato. Molto probabilmente questo accade perché il mio è più lento, dato che ho utilizzato count () invece dell'operatore "in" ... almeno un commento che dice che sarebbe stato fantastico, però :-)
Alan Franzoni

-2

Suggerirei:

if thing in thing_list:
  list_index = -1
else:
  list_index = thing_list.index(thing)

2
Il problema con questa soluzione è che "-1" è un indice valido nella lista (ultimo indice; il primo dalla fine). Il modo migliore per gestire questo sarebbe restituire False nel primo ramo della tua condizione.
FanaticD
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.