Dizionari e valori predefiniti


213

Supponendo che connectionDetailssia un dizionario Python, qual è il modo migliore, più elegante, più "pitonico" di refactoring del codice in questo modo?

if "host" in connectionDetails:
    host = connectionDetails["host"]
else:
    host = someDefaultValue

Risposte:


311

Come questo:

host = connectionDetails.get('host', someDefaultValue)

40
Si noti che il secondo argomento è un valore, non una chiave.
Marcin,

7
+1 per leggibilità, ma if/elseè molto più veloce. Ciò potrebbe o non potrebbe avere un ruolo.
Tim Pietzcker,

7
@Tim, puoi fornire un riferimento sul perché if/elseè più veloce?
nishantjr,

2
@Tim: avevo supposto che uno dei vantaggi dell'utilizzo di un linguaggio di livello superiore fosse che l'interprete sarebbe stato in grado di "vedere" all'interno delle funzioni e ottimizzarlo - che l'utente non avrebbe dovuto occuparsi tanto delle microottimizzazioni . Non è per questo che servono le compilation JIT?
nishantjr,

3
@nishantjr: Python (almeno CPython, la variante più comune) non ha compilation JIT. PyPy potrebbe davvero risolverlo più velocemente, ma non l'ho installato poiché Python standard è sempre stato abbastanza veloce per i miei scopi finora. In generale, è improbabile che importi nella vita reale - se devi fare un crunching numerico critico in termini di tempo, Python probabilmente non è la lingua scelta ...
Tim Pietzcker,

99

Puoi anche usare il defaultdictsimile così:

from collections import defaultdict
a = defaultdict(lambda: "default", key="some_value")
a["blabla"] => "default"
a["key"] => "some_value"

Puoi passare qualsiasi funzione ordinaria invece di lambda:

from collections import defaultdict
def a():
  return 4

b = defaultdict(a, key="some_value")
b['absent'] => 4
b['key'] => "some_value"

7
Sono venuto qui per un problema diverso rispetto alla domanda del PO, e la tua soluzione lo risolve esattamente.
0xc0de,

Lo farei +1 ma purtroppo non si adatta con getmetodi simili.
0xc0de,

Questa risposta mi è stata utile per garantire che le aggiunte a un dizionario includessero le chiavi predefinite. La mia implementazione è un po 'troppo lunga per essere descritta in una risposta StackOverflow, quindi ne ho scritto qui. persagen.com/2020/03/05/…
Victoria Stuart

24

Sebbene .get()sia un bel linguaggio, è più lento di if/else(e più lento di try/exceptse la presenza della chiave nel dizionario è prevedibile per la maggior parte del tempo):

>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="try:\n a=d[1]\nexcept KeyError:\n a=10")
0.07691968797894333
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="try:\n a=d[2]\nexcept KeyError:\n a=10")
0.4583777282275605
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="a=d.get(1, 10)")
0.17784020746671558
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="a=d.get(2, 10)")
0.17952161730158878
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="if 1 in d:\n a=d[1]\nelse:\n a=10")
0.10071221458065338
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="if 2 in d:\n a=d[2]\nelse:\n a=10")
0.06966537335119938

3
Non vedo ancora perché if/then sarebbe più veloce. In entrambi i casi richiedono una consultazione dei dizionari, e meno che l'invocazione di get()è così molto più lento, quello che i conti per il resto del rallentamento?
Jens,

1
@Jens: le chiamate di funzione sono costose.
Tim Pietzcker,

1
Quale non dovrebbe essere un grosso problema in un dizionario densamente popolato, giusto? Significa che la chiamata di funzione non avrà molta importanza se la ricerca effettiva è costosa. Probabilmente importa solo negli esempi di giocattoli.
AturSams

2
@zehelvion: la ricerca nel dizionario è O(1)indipendente dalle dimensioni del dizionario, quindi l'overhead della chiamata di funzione è rilevante.
Tim Pietzcker,

35
è strano se il sovraccarico di chiamare una funzione ti faccia decidere di non usare get. Usa ciò che i membri del tuo team possono leggere meglio.
Jochen Bedersdorfer,

19

Per più impostazioni predefinite diverse provare questo:

connectionDetails = { "host": "www.example.com" }
defaults = { "host": "127.0.0.1", "port": 8080 }

completeDetails = {}
completeDetails.update(defaults)
completeDetails.update(connectionDetails)
completeDetails["host"]  # ==> "www.example.com"
completeDetails["port"]  # ==> 8080

3
Questa è una buona soluzione idiomatica, ma c'è una trappola. Risultati imprevisti possono verificarsi se connectionDetails viene fornito con Noneo emptyString come uno dei valori nelle coppie chiave-valore. Il defaultsdizionario potrebbe potenzialmente sopprimere involontariamente uno dei suoi valori. (vedi anche stackoverflow.com/questions/6354436 )
dreftymac

9

Esiste un metodo nei dizionari Python per fare questo: dict.setdefault

connectionDetails.setdefault('host',someDefaultValue)
host = connectionDetails['host']

Tuttavia, questo metodo imposta il valore di connectionDetails['host']per someDefaultValuese la chiave hostnon è già definito, a differenza di quanto la domanda posta.


1
Si noti che setdefault()il valore ritorna, quindi questo funziona così: host = connectionDetails.setdefault('host', someDefaultValue). Fai attenzione che imposterà connectionDetails['host']il valore predefinito se la chiave non c'era prima.
ash108,

7

(questa è una risposta tardiva)

Un'alternativa è sottoclassare la dictclasse e implementare il __missing__()metodo, in questo modo:

class ConnectionDetails(dict):
    def __missing__(self, key):
        if key == 'host':
            return "localhost"
        raise KeyError(key)

Esempi:

>>> connection_details = ConnectionDetails(port=80)

>>> connection_details['host']
'localhost'

>>> connection_details['port']
80

>>> connection_details['password']
Traceback (most recent call last):
  File "python", line 1, in <module>
  File "python", line 6, in __missing__
KeyError: 'password'

4

Testando il sospetto di @Tim Pietzcker sulla situazione in PyPy (5.2.0-alpha0) per Python 3.3.5, trovo che in effetti entrambi .get()e il modo if/ elsefunzionano in modo simile. In realtà sembra che nel caso if / else ci sia anche una sola ricerca se la condizione e l'assegnazione coinvolgono la stessa chiave (confrontare con l'ultimo caso in cui ci sono due ricerche).

>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="try:\n a=d[1]\nexcept KeyError:\n a=10")
0.011889292989508249
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="try:\n a=d[2]\nexcept KeyError:\n a=10")
0.07310474599944428
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="a=d.get(1, 10)")
0.010391917996457778
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="a=d.get(2, 10)")
0.009348208011942916
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="if 1 in d:\n a=d[1]\nelse:\n a=10")
0.011475925013655797
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="if 2 in d:\n a=d[2]\nelse:\n a=10")
0.009605801998986863
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="if 2 in d:\n a=d[2]\nelse:\n a=d[1]")
0.017342638995614834

1

È possibile utilizzare una funzione lamba per questo come one-liner. Crea un nuovo oggetto a connectionDetails2cui si accede come una funzione ...

connectionDetails2 = lambda k: connectionDetails[k] if k in connectionDetails.keys() else "DEFAULT"

Adesso usa

connectionDetails2(k)

invece di

connectionDetails[k]

che restituisce il valore del dizionario se si ktrova nelle chiavi, altrimenti restituisce"DEFAULT"


Ti ho votato ma il problema con la tua soluzione è che i dadi funzionano con [] ma i lambda funzionano con ()
yukashima huksay,
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.