Wörterbücher und Standardwerte

213

Angenommen, es connectionDetailshandelt sich um ein Python-Wörterbuch. Was ist die beste, eleganteste und "pythonischste" Methode, um Code wie diesen umzugestalten?

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

Antworten:

311

So was:

host = connectionDetails.get('host', someDefaultValue)
MattH
quelle
40
Beachten Sie, dass das zweite Argument ein Wert und kein Schlüssel ist.
Marcin
7
+1 für die Lesbarkeit, ist aber if/elseviel schneller. Das könnte eine Rolle spielen oder auch nicht.
Tim Pietzcker
7
@ Tim, können Sie einen Hinweis geben, warum if/elseschneller ist?
Nishantjr
2
@Tim: Ich war davon ausgegangen, dass einer der Vorteile der Verwendung einer höheren Sprache darin besteht, dass der Interpreter in der Lage ist, die Funktionen zu „sehen“ und zu optimieren - dass der Benutzer sich nicht so sehr mit Mikrooptimierungen befassen muss . Ist das nicht das, wofür Dinge wie JIT-Kompilierung sind?
Nishantjr
3
@nishantjr: Python (zumindest CPython, die häufigste Variante) hat keine JIT-Kompilierung. PyPy könnte dies zwar schneller lösen, aber ich habe das nicht installiert, da Standard-Python für meine Zwecke bisher immer schnell genug war. Im Allgemeinen ist es unwahrscheinlich, dass es im wirklichen Leben eine Rolle spielt - wenn Sie zeitkritische Zahlen eingeben müssen, ist Python wahrscheinlich nicht die Sprache der Wahl ...
Tim Pietzcker
99

Sie können auch Folgendes verwenden defaultdict:

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

Sie können jede gewöhnliche Funktion anstelle von Lambda übergeben:

from collections import defaultdict
def a():
  return 4

b = defaultdict(a, key="some_value")
b['absent'] => 4
b['key'] => "some_value"
tamerlaha
quelle
7
Ich bin wegen eines anderen Problems als der Frage des OP hierher gekommen, und Ihre Lösung löst es genau.
0xc0de
Ich würde es +1 geben, aber leider passt es nicht zu getoder ähnlichen Methoden.
0xc0de
Diese Antwort war für mich hilfreich, um sicherzustellen, dass Ergänzungen zu einem Wörterbuch Standardschlüssel enthalten. Meine Implementierung ist etwas zu lang, um sie in einer StackOverflow-Antwort zu beschreiben, deshalb habe ich hier darüber geschrieben. persagen.com/2020/03/05/…
Victoria Stuart
24

Es .get()ist zwar eine nette Redewendung, aber langsamer als if/else(und langsamer als try/exceptwenn das Vorhandensein des Schlüssels im Wörterbuch die meiste Zeit erwartet werden kann):

>>> 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
Tim Pietzcker
quelle
3
Ich verstehe immer noch nicht, warum if/then es schneller gehen würde. Beide Fälle erfordern eine Wörterbuch - Lookup, und es sei denn , der Aufruf get()ist so viel langsamer, was sonst Konten für die Verlangsamung?
Jens
1
@Jens: Funktionsaufrufe sind teuer.
Tim Pietzcker
1
Was sollte in einem dicht besiedelten Wörterbuch keine große Sache sein, richtig? Dies bedeutet, dass der Funktionsaufruf keine große Rolle spielt, wenn die eigentliche Suche kostspielig ist. Es ist wahrscheinlich nur in Spielzeugbeispielen von Bedeutung.
AturSams
2
@zehelvion: Die Wörterbuchsuche ist O(1)unabhängig von der Wörterbuchgröße, daher ist der Overhead des Funktionsaufrufs relevant.
Tim Pietzcker
35
Es ist bizarr, wenn Sie sich aufgrund des Overheads beim Aufrufen einer Funktion gegen die Verwendung von get entscheiden würden. Verwenden Sie, was Ihre Teamkollegen am besten lesen können.
Jochen Bedersdorfer
19

Versuchen Sie für mehrere verschiedene Standardeinstellungen Folgendes:

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
Jerome Baum
quelle
3
Dies ist eine gute idiomatische Lösung, aber es gibt eine Falle. Unerwartete Ergebnisse können auftreten, wenn connectionDetails mit Noneoder dem emptyString als einem der Werte in den Schlüssel-Wert-Paaren geliefert wird. Im defaultsWörterbuch kann möglicherweise einer seiner Werte unbeabsichtigt ausgeblendet werden. (siehe auch stackoverflow.com/questions/6354436 )
dreftymac
9

In Python-Wörterbüchern gibt es eine Methode, um dies zu tun: dict.setdefault

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

Diese Methode setzt jedoch den Wert auf connectionDetails['host'], someDefaultValuewenn der Schlüssel hostnicht bereits definiert ist, anders als in der gestellten Frage.

Sriram
quelle
1
Beachten Sie, dass setdefault()der Wert zurückgegeben wird, sodass dies auch funktioniert : host = connectionDetails.setdefault('host', someDefaultValue). Beachten Sie nur, dass connectionDetails['host']der Standardwert festgelegt wird, wenn der Schlüssel zuvor nicht vorhanden war.
ash108
7

(Dies ist eine späte Antwort)

Eine Alternative besteht darin, die dictKlasse in Unterklassen zu unterteilen und die __missing__()Methode wie folgt zu implementieren :

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

Beispiele:

>>> 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'
Laurent LAPORTE
quelle
4

Wenn ich @Tim Pietzckers Verdacht auf die Situation in PyPy (5.2.0-alpha0) für Python 3.3.5 teste, stelle ich fest, dass tatsächlich beide .get()und die if/ elseway eine ähnliche Leistung erbringen . Tatsächlich scheint es im if / else-Fall nur eine einzige Suche zu geben, wenn die Bedingung und die Zuweisung denselben Schlüssel beinhalten (vergleiche mit dem letzten Fall, in dem es zwei Suchen gibt).

>>>> 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
Bis
quelle
1

Sie können hierfür eine Lamba-Funktion als Einzeiler verwenden. Erstellen Sie ein neues Objekt, auf connectionDetails2das wie eine Funktion zugegriffen wird ...

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

Jetzt benutzen

connectionDetails2(k)

anstatt

connectionDetails[k]

Dies gibt den Wörterbuchwert zurück, wenn er kin den Schlüsseln enthalten ist, andernfalls wird er zurückgegeben"DEFAULT"

Bobak Hashemi
quelle
Ich habe dich positiv bewertet, aber das Problem mit deiner Lösung ist, dass Diktate mit [] funktionieren, Lambdas jedoch mit ()
yukashima huksay