Python - 'if foo in dict' vs 'try: dict [foo]'


24

Questa è meno una domanda sulla natura della tipizzazione delle anatre e più sul rimanere pitoni, suppongo.

Prima di tutto - quando si tratta di dicts, in particolare quando la struttura del dict è abbastanza prevedibile e una determinata chiave non è in genere presente ma a volte lo è, penso innanzitutto a due approcci:

if myKey in dict:
    do_some_work(dict[myKey])
else:
    pass

E ovviamente l'approccio del "perdono contro permesso" di Ye Olde.

try:
    do_some_work(dict[myKey])
except KeyError:
    pass

Come un giornalista di Python, mi sembra di vedere quest'ultimo preferito molto, il che mi sembra strano solo perché in Python i documenti try/exceptssembrano essere preferiti quando c'è un vero errore, al contrario di, um ... assenza di successo?

Se il dict occasionale non ha la chiave in myDict ed è noto che non avrà sempre quella chiave, è un tentativo / se non contestualmente fuorviante? Questo non è un errore di programmazione, è solo un dato di fatto: questo dict non aveva quella chiave particolare.

Questo sembra particolarmente importante quando guardi la sintassi try / tranne / else, che sembra davvero utile quando si tratta di assicurarsi che try non stia rilevando troppi errori. Sei in grado di fare qualcosa del tipo:

try:
    foo += bar
except TypeError:
    pass
else:
    return some_more_work(foo)

Questo non porterà a inghiottire tutti i tipi di strani errori che sono probabilmente il risultato di un codice errato? Il codice sopra potrebbe impedirti di vedere che stai provando ad aggiungere 2 + {}e potresti non accorgerti mai che parte del tuo codice è andata terribilmente storto. Non suggerisco che dovremmo controllare tutti i tipi, ecco perché è Python e non JavaScript - ma di nuovo con il contesto di try / tranne, sembra che dovrebbe catturare il programma facendo qualcosa che non dovrebbe fare, invece di consentirgli di proseguire.

Mi rendo conto che l'esempio sopra è una sorta di argomento da uomo di paglia, ed è di fatto intenzionalmente negativo. Ma dato il credo pitonico di better to ask forgiveness than permissionnon posso fare a meno di pensare che si pone la questione di dove si trovi effettivamente la linea nella sabbia tra la corretta applicazione di if / else vs try / tranne, in particolare quando sai cosa aspettarti da i dati con cui stai lavorando.

Non sto nemmeno parlando di problemi di velocità o buone pratiche qui, sono solo un po 'confuso dal diagramma percepito di Venn in cui sembra che potrebbe andare in entrambi i modi, ma le persone sbagliano sul lato di un tentativo / tranne perché "qualcuno da qualche parte ha detto che era Pythonic". Ho tratto conclusioni errate sull'applicazione di questa sintassi?

python 

Concordo sul fatto che la tendenza a provare-tranne corre il rischio di nascondere errori. La mia opinione è che dovresti generalmente evitare di provare, tranne per le condizioni che ti aspetti di vedere (ovviamente alcune eccezioni si applicano a questa regola).
Gordon Bean,

Risposte:


26

Utilizzare invece il metodo get :

some_dict.get(the_key, default_value)

... dove default_valueè il valore restituito se the_keynon è in some_dict. Se si omette default_value, Noneviene restituito se manca la chiave.

In generale, in Python, le persone tendono a preferire provare / tranne che controllare prima qualcosa - vedere la voce EAFP nel glossario . Si noti che molte funzioni di "test per l'appartenenza" utilizzano eccezioni dietro le quinte.


Questo tipo di problema non è lo stesso? Se stiamo provando a lavorare con un dicte facendo un lavoro basato su ciò che viene trovato, sembra che if myDict.get(a_key) is not None: do_work(myDict[a_key])sia più prolisso e ancora un altro esempio di aspetto implicito prima di saltare - in più è IMHO leggermente meno leggibile. Forse non sto capendo dict.get(a_key, a_default)nel giusto contesto, ma sembra che sia un precursore a scrivere "non-switch-statement if / elses" o valori magici. Mi piace quello che fa questo metodo, lo esaminerò più in profondità.

1
@Stick - you do:, data = myDict.get(a_key, default)e o do_workfarà la cosa giusta quando viene dato default(ad es. None) Oppure lo fai if data: do_work(data). In entrambi i casi è necessaria una sola ricerca. (Potrebbe essere if data is not None: ..., in entrambi i casi, è ancora solo una ricerca e quindi la tua logica può prendere il sopravvento.)
Detly

1
Quindi ho deciso che dict.get () mi ha praticamente risparmiato un sacco di sforzi nel mio progetto attuale. Ha ridotto il mio conteggio delle righe, ripulito un sacco di try/except/elsespazzatura dall'aspetto terribile e nel complesso ha migliorato IMHO la leggibilità del mio codice. Ho imparato una lezione preziosa che avevo ascoltato prima, ma che sono riuscito a trascurare di prendere a cuore: "Se ti senti come se stessi scrivendo troppo codice, fermati e guarda le funzionalità della lingua". Grazie!!

@Stick - felice di sentirlo :) Mi piace quel consiglio, non l'ho mai sentito prima.
detenere il

1
Tecnicamente, qual è il più veloce?
Olivier Pons,

4

La chiave mancante è una regola o un'eccezione ?

Da un punto di vista pragmatico, lanciare / catturare è molto più lento quindi verificare se la chiave è nella tabella. Se la chiave mancante è un evento comune, è necessario utilizzare la condizione.

Un'altra cosa a favore della condizione è un'altra clausola: è molto più facile capire quando uno dei blocchi viene eseguito, si consideri che anche la stessa classe di eccezioni può essere generata da diverse istruzioni nel blocco try.

La clausola code in tranne non dovrebbe far parte del normale difetto (ad es. Se la chiave non è presente, aggiungerla) - dovrebbe gestire l'errore.


4
"Da un punto di vista pragmatico, lanciare / catturare è molto più lento quindi verificare se la chiave è nella tabella" - no, non lo è. Non necessariamente, comunque. L'ultima volta che ho controllato, le implementazioni più comuni di Python eseguono la versione try/ catchpiù velocemente.
detly

2
Gettare / catturare in Python non è poi così importante dal punto di vista delle prestazioni, nella misura in cui viene spesso usato per il controllo del flusso in Python. Inoltre, esiste la possibilità in alcune situazioni in cui tale dict possa essere modificato tra il test e l'accesso.
whatsisname

@whatsisname - Ho pensato di aggiungerlo alla mia risposta, ma TBH se hai rischi di razza del genere, hai problemi molto più grandi che EAFP vs LBYL: P
detly

1

Nello spirito di essere "pitonico" e, nello specifico, di scrivere l'anatra - il tentativo / tranne mi sembra quasi fragile.

Tutto quello che vuoi davvero è qualcosa con accesso simile a una dict, ma __getitem__può essere ignorato per fare qualcosa non esattamente come ti aspetti. Ad esempio, utilizzando defaultdictdalla libreria standard:

In [1]: from collections import defaultdict

In [2]: foo = defaultdict(int)

In [3]: foo['a'] = 1

In [4]: foo['b']  # Someone did access checking with a try/except exactly as you have in the question
Out[4]: 0

In [5]: 'a' in foo
Out[5]: True

In [6]: 'b' in foo  # We never set it, but now it exists!
Out[6]: True

In [7]: 'c' in foo
Out[7]: False

Poiché il valore di ritorno predefinito è Falsy, il controllo degli accessi in questo modo funzionerà quasi sempre bene. Sembra che anche il codice sia più semplice, motivo per cui io e i miei colleghi lo abbiamo fatto per mesi.

Sfortunatamente, alcuni mesi dopo, queste chiavi extra hanno effettivamente causato problemi e bug difficili da rintracciare, perché sono 0diventati un valore valido. E a volte ci sarebbe utilizzare inper verificare se esistesse una chiave, rendendo il codice fare cose diverse, quando avrebbe dovuto essere identico.

Il motivo per cui suggerisco che sembri rotto è perché nello spirito di Python nella tipizzazione delle anatre, tutto ciò che dovresti desiderare è qualcosa di simile a una dict e si defaultdictadatta perfettamente a questo requisito, come qualsiasi oggetto che potresti creare da cui erediti dict. Non puoi garantire che il chiamante non cambierà la sua implementazione in seguito, quindi dovresti fare la cosa con il minor numero di effetti collaterali.

Usa la prima versione if myKey in mydict,.


Inoltre, tieni presente che si tratta in particolare dell'esempio nella domanda. Il credo di Python di "Meglio chiedere perdono che permesso" è in gran parte inteso a garantire che tu agisca correttamente quando non ottieni ciò che desideri. Ad esempio, controllare se esiste un file e quindi provare a leggere da esso - almeno 3 cose che posso pensare dalla parte superiore della mia testa possono andare storte:

  • Una condizione di competizione è che il file potrebbe essere stato cancellato / spostato / ecc
  • Non hai i permessi di lettura sul file
  • Il file è stato danneggiato

Il primo su cui puoi provare a "chiedere il permesso", ma per natura delle condizioni di gara non funzionerebbe necessariamente sempre; il secondo è troppo facile da dimenticare per controllare; e il terzo non può essere verificato senza provare a leggere il file. In ogni caso, quello che fai dopo aver fallito sarà quasi sicuramente lo stesso, quindi in questo esempio è meglio "chiedere perdono" usando un tentativo / salvo.

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.