Perché l'elenco non ha un metodo "get" sicuro come il dizionario?


264

Perché l'elenco non ha un metodo "get" sicuro come il dizionario?

>>> d = {'a':'b'}
>>> d['a']
'b'
>>> d['c']
KeyError: 'c'
>>> d.get('c', 'fail')
'fail'

>>> l = [1]
>>> l[10]
IndexError: list index out of range

1
Gli elenchi vengono utilizzati per scopi diversi rispetto ai dizionari. Get () non è necessario per i casi d'uso tipici dell'elenco. Tuttavia, per il dizionario get () è spesso utile.
Mons.

42
Puoi sempre ottenere un elenco secondario vuoto da un elenco senza aumentare IndexError se invece chiedi una sezione: l[10:11]anziché l[10], ad esempio. () L'elenco secondario avrà l'elemento desiderato se esiste)
jsbueno,

56
Contrariamente ad alcuni qui, sostengo l'idea di una cassaforte .get. Sarebbe l'equivalente di l[i] if i < len(l) else default, ma più leggibile, più conciso e consentendo idi essere un'espressione senza dover ricalcolarla
Paul Draper,

6
Oggi avrei voluto che esistesse. Uso una funzione costosa che restituisce un elenco, ma volevo solo il primo elemento, o Nonese uno non esistesse. Sarebbe stato bello dirlo, x = expensive().get(0, None)quindi non avrei dovuto mettere l'inutile ritorno del costoso in una variabile temporanea.
Ryan Hiebert,

2
@Ryan la mia risposta può aiutarti a stackoverflow.com/a/23003811/246265
Jake

Risposte:


112

In definitiva, probabilmente non ha un .getmetodo sicuro perché a dictè una raccolta associativa (i valori sono associati ai nomi) in cui è inefficace controllare se è presente una chiave (e restituire il suo valore) senza generare un'eccezione, mentre è super banale per evitare eccezioni nell'accesso agli elementi dell'elenco (in quanto il lenmetodo è molto veloce). Il .getmetodo ti consente di interrogare il valore associato a un nome, non di accedere direttamente al 37 ° elemento nel dizionario (che sarebbe più simile a quello che stai chiedendo al tuo elenco).

Naturalmente, puoi facilmente implementarlo da solo:

def safe_list_get (l, idx, default):
  try:
    return l[idx]
  except IndexError:
    return default

Si potrebbe persino eseguire il monkeypatch sul __builtins__.listcostruttore in __main__, ma sarebbe un cambiamento meno pervasivo poiché la maggior parte del codice non lo utilizza. Se volessi semplicemente usarlo con elenchi creati con il tuo codice, potresti semplicemente sottoclassare liste aggiungere il getmetodo.


24
Python non consente il monkeypatching di tipi predefiniti comelist
Imran,

7
@ CSZ: .getrisolve un problema che gli elenchi non hanno - un modo efficace per evitare eccezioni quando si ottengono dati che potrebbero non esistere. È super banale e molto efficiente sapere cos'è un indice di elenco valido, ma non esiste un modo particolarmente valido per farlo per i valori chiave in un dizionario.
Nick Bastin,

10
Non penso affatto che si tratti di efficienza: verificare se una chiave è presente in un dizionario e / o restituire un elemento O(1). Non sarà così veloce in termini grezzi come il controllo len, ma dal punto di vista della complessità lo sono tutti O(1). La risposta giusta è quella tipica di uso / semantica ...
Mark Longair,

3
@Mark: non tutti gli O (1) sono uguali. Inoltre, dictè solo il caso migliore O (1), non tutti i casi.
Nick Bastin,

4
Penso che alla gente manchi il punto qui. La discussione non dovrebbe riguardare l'efficienza. Si prega di interrompere con l'ottimizzazione prematura. Se il tuo programma è troppo lento, stai abusando .get()o hai problemi altrove nel tuo codice (o ambiente). Il punto di usare un tale metodo è la leggibilità del codice. La tecnica "vaniglia" richiede quattro righe di codice in ogni luogo in cui ciò deve essere fatto. La .get()tecnica richiede solo uno e può essere facilmente concatenata con successive chiamate di metodo (ad es my_list.get(2, '').uppercase().).
Tyler Crompton,

67

Funziona se vuoi il primo elemento, come my_list.get(0)

>>> my_list = [1,2,3]
>>> next(iter(my_list), 'fail')
1
>>> my_list = []
>>> next(iter(my_list), 'fail')
'fail'

So che non è esattamente quello che hai chiesto, ma potrebbe aiutare gli altri.


7
meno pitonico di funzionale programmazione-esque
Eric

next(iter(my_list[index:index+1]), 'fail')Consente di qualsiasi indice, non solo 0. o meno FP, ma forse più Pythonic, e quasi certamente più leggibile: my_list[index] if index < len(my_list) else 'fail'.
alphabetasoup,

47

Probabilmente perché non aveva molto senso per la semantica delle liste. Tuttavia, puoi facilmente crearne uno tuo tramite la sottoclasse.

class safelist(list):
    def get(self, index, default=None):
        try:
            return self.__getitem__(index)
        except IndexError:
            return default

def _test():
    l = safelist(range(10))
    print l.get(20, "oops")

if __name__ == "__main__":
    _test()

5
Questo è di gran lunga il più pitonico che risponde all'OP. Nota che puoi anche estrarre un elenco secondario, che è un'operazione sicura in Python. Dato mylist = [1, 2, 3], puoi provare a estrarre il nono elemento con mylist [8: 9] senza attivare un'eccezione. È quindi possibile verificare se l'elenco è vuoto e, nel caso in cui non sia vuoto, estrarre il singolo elemento dall'elenco restituito.
jose.angel.jimenez,

1
Questa dovrebbe essere la risposta accettata, non gli altri hack non di tipo pitone, soprattutto perché conserva la simmetria con i dizionari.
Eric,

1
Non c'è nulla di pitonico nel sottoclasse delle tue liste solo perché hai bisogno di un getmetodo carino . La leggibilità conta. E la leggibilità soffre di ogni ulteriore classe non necessaria. Basta usare iltry / except approccio senza creare sottoclassi.
Jeyekomon,

@Jeyekomon È perfettamente Pythonic per ridurre la piastra di caldaia mediante la sottoclasse.
Keith,

42

Invece di usare .get, usare così dovrebbe essere ok per le liste. Solo una differenza di utilizzo.

>>> l = [1]
>>> l[10] if 10 < len(l) else 'fail'
'fail'

15
Questo non riesce se proviamo a ottenere l'ultimo elemento con -1.
pretobomba,

Si noti che ciò non funziona per gli oggetti elenco collegati in modo circolare. Inoltre, la sintassi provoca quello che mi piace chiamare un "blocco di scansione". Durante la scansione del codice per vedere cosa fa, questa è una linea che mi rallenterebbe per un momento.
Tyler Crompton,

inline if / else non funziona con Python più vecchio come 2.6 (o è 2.5?)
Eric

3
@TylerCrompton: non esiste un elenco collegato in modo circolare in Python. Se ne hai scritto uno da solo sei libero di aver implementato un .getmetodo (tranne che non sono sicuro di come spiegheresti cosa significa l'indice in questo caso, o perché mai fallirebbe).
Nick Bastin,

Un'alternativa che gestisce gli indici negativi fuori limite sarebbelst[i] if -len(lst) <= i < len(l) else 'fail'
mic

17

Prova questo:

>>> i = 3
>>> a = [1, 2, 3, 4]
>>> next(iter(a[i:]), 'fail')
4
>>> next(iter(a[i + 1:]), 'fail')
'fail'

1
mi piace questo, anche se richiede prima la creazione di un nuovo elenco secondario.
Rick supporta Monica il

15

Crediti a jose.angel.jimenez


Per i fan di "oneliner" ...


Se si desidera il primo elemento di un elenco o se si desidera un valore predefinito se l'elenco è vuoto, provare:

liste = ['a', 'b', 'c']
value = (liste[0:1] or ('default',))[0]
print(value)

ritorna a

e

liste = []
value = (liste[0:1] or ('default',))[0]
print(value)

ritorna default


Esempi per altri elementi ...

liste = ['a', 'b', 'c']
print(liste[0:1])  # returns ['a']
print(liste[1:2])  # returns ['b']
print(liste[2:3])  # returns ['c']

Con fallback predefinito ...

liste = ['a', 'b', 'c']
print((liste[0:1] or ('default',))[0])  # returns a
print((liste[1:2] or ('default',))[0])  # returns b
print((liste[2:3] or ('default',))[0])  # returns c

Testato con Python 3.6.0 (v3.6.0:41df79263a11, Dec 22 2016, 17:23:13)


1
Breve alternativa: value, = liste[:1] or ('default',). Sembra che tu abbia bisogno delle parentesi.
qräbnö

13

La cosa migliore che puoi fare è convertire l'elenco in un dict e quindi accedervi con il metodo get:

>>> my_list = ['a', 'b', 'c', 'd', 'e']
>>> my_dict = dict(enumerate(my_list))
>>> print my_dict
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e'}
>>> my_dict.get(2)
'c'
>>> my_dict.get(10, 'N/A')

20
Una soluzione ragionevole, ma difficilmente "la cosa migliore che puoi fare".
Tripleee,

3
Molto inefficiente però. Nota: Invece di quello zip range len, si potrebbe semplicemente usaredict(enumerate(my_list))
Marian

3
Questa non è la cosa migliore, è la cosa peggiore che potresti fare.
erikbwork,

3
È la cosa peggiore se consideri le prestazioni ... se ti preoccupi delle prestazioni, non codifichi in un linguaggio interpretato come Python. Trovo questa soluzione usando un dizionario piuttosto elegante, potente e pitonico. Le prime ottimizzazioni sono comunque malvagie, quindi facciamo un passo avanti e vediamo dopo che è un collo di bottiglia.
Eric

7

Quindi ho fatto qualche ricerca in più su questo e si scopre che non c'è nulla di specifico per questo. Mi sono emozionato quando ho trovato list.index (valore), restituisce l'indice di un elemento specificato, ma non c'è nulla per ottenere il valore in un indice specifico. Quindi se non vuoi usare la soluzione safe_list_get che penso sia abbastanza buona. Di seguito sono riportati alcuni esempi di istruzioni che possono svolgere il lavoro per te in base allo scenario:

>>> x = [1, 2, 3]
>>> el = x[4] if len(x) > 4 else 'No'
>>> el
'No'

Puoi anche usare Nessuno invece di 'No', il che ha più senso .:

>>> x = [1, 2, 3]
>>> i = 2
>>> el_i = x[i] if len(x) == i+1 else None

Inoltre, se vuoi ottenere solo il primo o l'ultimo elemento nell'elenco, questo funziona

end_el = x[-1] if x else None

Puoi anche trasformarli in funzioni ma mi è comunque piaciuta la soluzione di eccezione IndexError. Ho sperimentato una versione ridotta della safe_list_getsoluzione e l'ho resa un po 'più semplice (nessun valore predefinito):

def list_get(l, i):
    try:
        return l[i]
    except IndexError:
        return None

Non ho fatto benchmark per vedere cosa è più veloce.


1
Non proprio pitonico.
Eric,

@Eric quale snippet? Penso che il tentativo, tranne che abbia più senso guardandolo di nuovo.
Radtek,

Una funzione autonoma non è pitonica. Le eccezioni sono in effetti un po 'più pitoniche ma non di gran lunga in quanto è un modello così comune nei linguaggi di programmazione. Cosa c'è di più pythonic è un nuovo oggetto che estende il tipo incorporato listsubclassandolo. In questo modo il costruttore può prendere una listo qualsiasi cosa si comporti come un elenco e la nuova istanza si comporta proprio come una list. Vedi la risposta di Keith di seguito, che dovrebbe essere quella accettata IMHO.
Eric,

1
@Eric Ho analizzato la domanda non come OOP specifica ma come "perché gli elenchi non hanno analogia con la dict.get()restituzione di un valore predefinito da un riferimento all'indice dell'elenco invece di doverli intercettare IndexError? Quindi si tratta davvero della funzione lingua / libreria (e non OOP rispetto al contesto di FP) Inoltre, si deve probabilmente qualificare il tuo uso di "pythonic" come forse WWGD (poiché il suo disprezzo per FP Python è ben noto) e non necessariamente soddisfare solo PEP8 / 20.
cowbert

1
el = x[4] if len(x) == 4 else 'No'- vuoi dire len(x) > 4? x[4]è fuori limite se len(x) == 4.
Mic

4

I dizionari sono per le ricerche. Ha senso chiedere se esiste o meno una voce. Le liste sono di solito iterate. Non è comune chiedere se esiste L [10] ma piuttosto se la lunghezza di L è 11.


Sì, d'accordo con te. Ma ho appena analizzato l'URL relativo della pagina "/ group / Page_name". Dividilo per '/' e volevo verificare se PageName è uguale a una determinata pagina. Sarebbe comodo scrivere qualcosa come [url.split ('/'). Get_from_index (2, None) == "lalala"] invece di fare un controllo extra per la lunghezza o rilevare un'eccezione o scrivere la propria funzione. Probabilmente hai ragione, è semplicemente considerato insolito. Comunque non sono ancora d'accordo con questo =)
CSZ il

@Nick Bastin: niente di male. Si tratta di semplicità e velocità di codifica.
CSZ,

Sarebbe anche utile se si volesse utilizzare gli elenchi come un dizionario più efficiente in termini di spazio nei casi in cui le chiavi sono interi consecutivi. Naturalmente l'esistenza di indicizzazione negativa lo ferma già.
Antimonio

-1

Il tuo caso d'uso è fondamentalmente rilevante solo quando si eseguono array e matrici di lunghezza fissa, in modo da sapere quanto tempo sono prima. In tal caso, in genere li crei anche prima di riempirli a mano con Nessuno o 0, in modo che qualsiasi indice che utilizzerai già esiste.

Potresti dire questo: ho bisogno di .get () sui dizionari abbastanza spesso. Dopo dieci anni come programmatore a tempo pieno non credo di averne mai avuto bisogno in un elenco. :)


Che ne dici del mio esempio nei commenti? Cosa è più semplice e leggibile? (url.split ('/'). getFromIndex (2) == "lalala") OPPURE (risultato = url.split (); len (risultato)> 2 e risultato [2] == "lalala"). E sì, so di poter scrivere questa funzione da solo =) ma sono stato sorpreso che tale funzione non sia integrata.
CSZ,

1
Dico nel tuo caso che lo stai facendo male. La gestione degli URL deve essere eseguita tramite route (pattern matching) o traversal di oggetti. Ma, per rispondere alla tua caso specifico: 'lalala' in url.split('/')[2:]. Ma il problema con la tua soluzione qui è che guardi solo al secondo elemento. Cosa succede se l'URL è '/ monkeybonkey / lalala'? Otterrai un Trueanche se l'URL non è valido.
Lennart Regebro,

Ho preso solo il secondo elemento perché avevo bisogno solo del secondo elemento. Ma sì, le fette sembrano una valida alternativa di lavoro
CSZ,

@CSZ: Ma poi il primo elemento viene ignorato e in quel caso potresti saltarlo. :) Vedi cosa intendo, l'esempio non funziona così bene nella vita reale.
Lennart Regebro,
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.