2010-01-25 9 views
66

Ho codice che sembra qualcosa di simile:Il modo migliore per gestire list.index (potrebbe non esistere) in python?

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

ok così che è semplificato, ma si ottiene l'idea. Ora thing potrebbe non essere effettivamente nella lista, nel qual caso voglio passare -1 come thing_index. In altre lingue questo è quello che ci si aspetta index() da restituire se non è stato possibile trovare l'elemento. Infatti getta un ValueError.

avrei potuto fare questo:

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

Ma questo si sente sporca, più io non so se ValueError potrebbe essere sollevato per qualche altro motivo. Sono venuto con la seguente soluzione basata su 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] 

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

+1

s/catch/eccetto /: o) –

+4

"... nel qual caso voglio passare -1 come' thing_index'" - Questo è decisamente non-Pythonic. Passare un valore token (senza significato) nel caso in cui un'operazione non riesca è disapprovato - le eccezioni sono davvero la giusta via qui. Soprattutto perché 'thing_list [-1]' è un'espressione valida, che significa l'ultima voce nella lista. –

+0

@jellybean: * facepalm * ... trova il codificatore java: P – Draemon

risposta

45

Non c'è nulla di "sporco" nell'usare la clausola try-except. Questo è il modo pititico. ValueError verrà generato solo dal metodo .index, perché è l'unico codice che hai lì!

Per rispondere il commento:
In Python, easier to ask forgiveness than to get permission filosofia è ben definito, e non index non fa salire questo tipo di errore per tutte le altre questioni. Non che io possa pensare a nessuno.

+14

Sicuramente le eccezioni sono per casi eccezionali, e questo è difficile.Non avrei questo problema se l'eccezione fosse più specifica di ValueError. – Draemon

+1

So che può essere lanciato solo da quel * metodo * ma è garantito che venga gettato solo per quella * ragione *? Non che io possa pensare ad un altro motivo per cui l'indice fallirebbe ... ma poi non sono eccezioni per esattamente quelle cose a cui non potresti pensare? – Draemon

+0

@Draemon: Ecco perché controlla solo 'ValueError' e non solo qualsiasi forma di eccezione. – MAK

13

Il dict type ha un get function, dove se la chiave non esiste nel dizionario, il secondo argomento su get è il valore che deve restituire. Allo stesso modo c'è setdefault, che restituisce il valore nel dict se la chiave esiste, altrimenti imposta il valore in base al parametro predefinito e quindi restituisce il parametro predefinito.

È possibile estendere il tipo list in modo da avere un metodo getindexdefault.

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

che potrebbe poi essere usato come:

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

Non so il motivo per cui si dovrebbe pensare è sporco ... a causa del l'eccezione? se vuoi un oneliner, eccolo:

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

ma vorrei sconsigliarlo; Penso che la soluzione di Ross Rogers sia la migliore, utilizzare un oggetto per incapsulare il tuo comportamento desiderato, non provare a spingere la lingua ai suoi limiti al costo della leggibilità.

+1

Sì, a causa dell'eccezione. Il tuo codice farà due ricerche lineari no? Non che le prestazioni siano davvero importanti qui. La soluzione SuperDuperList è buona, 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 il mio aspetto estetico). – Draemon

+0

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

+1

È curioso che la mia risposta abbia due downvotes, mentre quella di Emil Ivanov, mentre semanticamente identica, è quella dei più upvoted. Molto probabilmente questo accade perché il mio è più lento, dal momento che ho impiegato count() anziché l'operatore "in" ... almeno un commento dicendo che sarebbe stato grandioso, però :-) –

5

Non c'è niente di sbagliato nel codice che utilizza ValueError. Ecco un altro one-liner se desideri evitare eccezioni:

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

È quel Python 2.6? So che non l'ho menzionato, ma sto usando 2.5. Questo è probabilmente quello che farei in 2.6 – Draemon

+1

@Draemon: Sì, la funzione 'next()' esiste in Python 2.6+. Ma è facile da implementare per 2.5, vedere [next() implementazione della funzione per Python 2.5] (http://stackoverflow.com/questions/500578/is-there-an-alternative-way-of-calling-next-on -python-generators/500609 # 500609) – jfs

3

Questo problema è una delle filosofie linguistiche. Ad esempio, in Java 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 flow control.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 eccezioni per indicare il normale flusso del programma, come ad esempio il sollevamento di un ValueError come stiamo discutendo qui. Non c'è niente di "sporco" in questo stile in Python e ce ne sono molti altri da dove viene. Un esempio ancora più comune è StopIteration exception che viene generato dal metodo next() di un iteratore per segnalare che non ci sono altri valori.

+0

In realtà, il JDK genera * via * troppe eccezioni controllate, quindi non sono sicuro che la filosofia sia effettivamente applicata a Java. Non ho un problema di per sé con 'StopIteration' perché è chiaramente definito cosa significhi l'eccezione. 'ValueError' è solo un po 'troppo generico. – Draemon

+0

Mi riferivo all'idea che le eccezioni non dovrebbero essere utilizzate per il controllo del flusso: http://c2.com/cgi/wiki?DontUseExceptionsForFlowControl, non tanto il numero di eccezioni controllate che Java ha un'intera discussione: http: //www.mindview.net/Etc/Discussions/CheckedExceptions –

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

Una riga. Semplice. Nessuna eccezione.

+18

Semplice sì, ma farà due ricerche lineari e mentre le prestazioni non sono un problema per se, ciò sembra eccessivo. – Draemon

+2

@Draemon: Accetto - che farà 2 passaggi - ma è improbabile che da un code-code di migliaia di linee questo sarà il collo di bottiglia. :) Si può sempre opt-in per una soluzione imperativa con 'for'. –

1

Che dire di questo:

otherfunction(thing_collection, thing) 

Invece di esporre qualcosa di così implementazione-dipendente come un indice di lista in una funzione di interfaccia, passare la raccolta e la cosa e lasciare che otherfunction accordo con il "test di adesione" problemi . Se otherfunction è scritto per essere raccolta di tipo agnostico, allora sarebbe probabilmente iniziare con:

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

che funziona se thing_collection è un elenco, tuple, impostare o dict.

Questo è forse più chiara rispetto:

if thing_index != MAGIC_VALUE_INDICATING_NOT_A_MEMBER: 

che è il codice che già avete nella otherfunction.

-2

mi piacerebbe suggerire:

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 in elenco (ultimo indice, il primo dalla fine). Un modo migliore per gestirlo sarebbe restituire False nel primo ramo della tua condizione. – FanaticD

0

Ho lo stesso problema con il ".index()" metodo su liste. Non ho alcun problema con il fatto che getta un'eccezione, ma non sono assolutamente d'accordo con il fatto che si tratta di un'eccezione ValueError non descrittiva. Potrei capire se sarebbe stato un errore IndexError, però.

Posso capire perché restituire "-1" sarebbe un problema anche perché è un indice valido in Python. Ma realisticamente, io mai aspettiamo un metodo ".index()" per restituire un numero negativo.

Qui va un solo liner (ok, è una linea piuttosto lunga ...), passa nell'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 
>>> 
Problemi correlati