`if key in dict` vs.` try / Except` - welche Redewendung ist besser lesbar?

93

Ich habe eine Frage zu Redewendungen und Lesbarkeit, und es scheint einen Konflikt zwischen Python-Philosophien für diesen speziellen Fall zu geben:

Ich möchte Wörterbuch A aus Wörterbuch B erstellen. Wenn in B kein bestimmter Schlüssel vorhanden ist, tun Sie nichts und fahren Sie fort.

Welcher Weg ist besser?

try:
    A["blah"] = B["blah"]
except KeyError:
    pass

oder

if "blah" in B:
    A["blah"] = B["blah"]

"Tu und bitte um Vergebung" vs. "Einfachheit und Aussagekraft".

Welches ist besser und warum?

LeeMobile
quelle
1
Das zweite Beispiel könnte besser als if "blah" in B.keys()oder geschrieben sein if B.has_key("blah").
Girasquid
2
funktioniert A.update(B)bei dir nicht
SilentGhost
21
@Luke: has_keywurde zugunsten von inund Überprüfung B.keys()einer O (1) -Operation in eine O (n) -Operation veraltet .
Kindall
4
@ Luke: nicht ist es nicht. .has_keyist veraltet und keyserstellt nicht benötigte Liste in py2k und ist in py3k redundant
SilentGhost
2
'bauen' A, wie in, A ist zunächst leer? Und wir wollen nur bestimmte Schlüssel? Verwenden Sie ein Verständnis : A = dict((k, v) for (k, v) in B if we_want_to_include(k)).
Karl Knechtel

Antworten:

74

Ausnahmen sind keine Bedingungen.

Die bedingte Version ist klarer. Das ist natürlich: Dies ist eine unkomplizierte Flusskontrolle, für die Bedingungen ausgelegt sind, keine Ausnahmen.

Die Ausnahmeversion wird hauptsächlich als Optimierung verwendet, wenn diese Suchvorgänge in einer Schleife ausgeführt werden: Bei einigen Algorithmen können Tests aus inneren Schleifen entfernt werden. Diesen Vorteil hat es hier nicht. Es hat den kleinen Vorteil, dass es nicht "blah"zweimal gesagt werden muss, aber wenn Sie viele davon tun, sollten Sie wahrscheinlich move_keytrotzdem eine Hilfsfunktion haben.

Im Allgemeinen würde ich dringend empfehlen, standardmäßig bei der bedingten Version zu bleiben, es sei denn, Sie haben einen bestimmten Grund, dies nicht zu tun. Bedingungen sind der naheliegende Weg, um dies zu tun. Dies ist normalerweise eine starke Empfehlung, eine Lösung einer anderen vorzuziehen.

Glenn Maynard
quelle
3
Ich stimme nicht zu Wenn Sie sagen "mach X und wenn das nicht funktioniert, mach Y". Hauptgrund gegen die bedingte Lösung hier ist, dass Sie "blah"häufiger schreiben müssen, was zu einer fehleranfälligeren Situation führt.
glglgl
6
Insbesondere in Python ist EAFP sehr verbreitet.
glglgl
8
Diese Antwort wäre für jede Sprache, die ich kenne, außer Python, richtig.
Tomáš Zato - Wiedereinsetzung Monica
3
Wenn Sie Ausnahmen verwenden, als wären sie Bedingungen in Python, hoffe ich, dass niemand anderes sie lesen muss.
Glenn Maynard
Wie lautet das endgültige Urteil? :)
floatingpurr
59

Es gibt auch einen dritten Weg, der sowohl Ausnahmen als auch die doppelte Suche vermeidet. Dies kann wichtig sein, wenn die Suche teuer ist:

value = B.get("blah", None)
if value is not None: 
    A["blah"] = value

Für den Fall , erwarten Sie das Wörterbuch enthalten NoneWerte, können Sie einige exotischere Konstanten wie verwenden NotImplemented, Ellipsisoder einen neuen machen:

MyConst = object()
def update_key(A, B, key):
    value = B.get(key, MyConst)
    if value is not MyConst: 
        A[key] = value

Auf jeden Fall update()ist die Verwendung für mich die am besten lesbare Option:

a.update((k, b[k]) for k in ("foo", "bar", "blah") if k in b)
lqc
quelle
14

Soweit ich weiß, möchten Sie Diktat A mit Schlüssel-Wert-Paaren aus Diktat B aktualisieren

update ist eine bessere Wahl.

A.update(B)

Beispiel:

>>> A = {'a':1, 'b': 2, 'c':3}
>>> B = {'d': 2, 'b':5, 'c': 4}
>>> A.update(B)
>>> A
{'a': 1, 'c': 4, 'b': 5, 'd': 2}
>>> 
pyfunc
quelle
"Wenn ein bestimmter Schlüssel in B nicht vorhanden ist" Entschuldigung, hätte klarer sein sollen, aber ich möchte nur über Werte kopieren, wenn bestimmte Schlüssel in B vorhanden sind. Nicht alle in B.
LeeMobile
1
@ LeeMobile -A.update({k: v for k, v in B.iteritems() if k in specificset})
Omnifarious
8

Direktes Zitat aus dem Python-Performance-Wiki:

Mit Ausnahme des ersten Males schlägt der Test der if-Anweisung jedes Mal fehl, wenn ein Wort angezeigt wird. Wenn Sie eine große Anzahl von Wörtern zählen, werden viele wahrscheinlich mehrmals vorkommen. In einer Situation, in der die Initialisierung eines Werts nur einmal erfolgt und die Erhöhung dieses Werts um ein Vielfaches erfolgt, ist es billiger, eine try-Anweisung zu verwenden.

Es scheint also, dass beide Optionen je nach Situation realisierbar sind. Weitere Informationen finden Sie unter folgendem Link: Try-Except-Performance

Sami Lehtinen
quelle
Das ist eine interessante Lektüre, aber ich denke etwas unvollständig. Das verwendete Diktat hat nur 1 Element und ich vermute, dass größere Diktate einen signifikanten Einfluss auf die Leistung haben werden
user2682863
3

Ich denke, die allgemeine Regel hier ist A["blah"]normalerweise, wenn ja, versuchen Sie es - außer ist gut, wenn nicht, dann verwenden Sieif "blah" in b:

Ich denke, "versuchen" ist in der Zeit billig, aber "außer" ist teurer.

Neil
quelle
10
Gehen Sie den Code nicht standardmäßig aus Optimierungssicht an. Gehen Sie es aus Sicht der Lesbarkeit und Wartbarkeit an. Sofern das Ziel nicht speziell die Optimierung ist, sind dies die falschen Kriterien (und wenn es sich um Optimierung handelt, lautet die Antwort Benchmarking, nicht Raten).
Glenn Maynard
Ich hätte wahrscheinlich den letzten Punkt in Klammern setzen sollen oder irgendwie vage - mein Hauptpunkt war der erste und ich denke, er hat den zusätzlichen Vorteil des zweiten.
Neil
3

Ich denke, das zweite Beispiel ist das, wofür Sie sich entscheiden sollten, es sei denn, dieser Code macht Sinn:

try:
    A["foo"] = B["foo"]
    A["bar"] = B["bar"]
    A["baz"] = B["baz"]
except KeyError:
    pass

Beachten Sie, dass der Code abgebrochen wird, sobald ein Schlüssel nicht vorhanden ist B. Wenn dieser Code sinnvoll ist, sollten Sie die Ausnahmemethode verwenden, andernfalls die Testmethode. Meiner Meinung nach ist es viel einfacher zu lesen als die Ausnahmemethode, da es kürzer ist und die Absicht klar zum Ausdruck bringt.

Natürlich sind die Leute, die Ihnen sagen, dass Sie verwenden updatesollen, korrekt. Wenn Sie eine Version von Python verwenden, die das Verständnis von Wörterbüchern unterstützt, würde ich diesen Code sehr bevorzugen:

updateset = {'foo', 'bar', 'baz'}
A.update({k: B[k] for k in updateset if k in B})
Allgegenwärtig
quelle
"Denken Sie daran, dass der Code abgebrochen wird, sobald ein Schlüssel in B vorhanden ist." - Aus diesem Grund empfiehlt es sich, nur das absolute Minimum in den try: -Block zu setzen. In der Regel handelt es sich dabei um eine einzelne Zeile. Das erste Beispiel wäre besser als Teil einer Schleife, wiefor key in ["foo", "bar", "baz"]: try: A[key] = B[key]
Zim
2

In anderen Sprachen gilt die Regel, Ausnahmen für außergewöhnliche Bedingungen zu reservieren, dh Fehler, die bei regelmäßiger Verwendung nicht auftreten. Ich weiß nicht, wie diese Regel für Python gilt, da StopIteration nach dieser Regel nicht existieren sollte.

Mark Ransom
quelle
Ich denke, diese Kastanie stammt aus Sprachen, in denen die Behandlung von Ausnahmen teuer ist und daher erhebliche Auswirkungen auf die Leistung haben kann. Ich habe nie eine wirkliche Rechtfertigung oder Argumentation dahinter gesehen.
John La Rooy
@ JohnLaRooy Nein, Leistung ist nicht wirklich der Grund. Ausnahmen sind eine Art nicht-lokales Goto , von dem einige Leute annehmen , dass es die Lesbarkeit des Codes beeinträchtigt. Die Verwendung von Ausnahmen auf diese Weise wird in Python jedoch als idiomatisch angesehen, sodass das oben Gesagte nicht gilt.
Ian Goldby
Bedingte Rückgaben sind auch "nicht lokal" und viele Leute bevorzugen diesen Stil, anstatt Sentinels am Ende des Codeblocks zu überprüfen.
Cowbert
1

Persönlich neige ich zur zweiten Methode (aber mit has_key):

if B.has_key("blah"):
  A["blah"] = B["blah"]

Auf diese Weise besteht jede Zuweisungsoperation nur aus zwei Zeilen (anstelle von 4 mit try / exception), und alle Ausnahmen, die ausgelöst werden, sind echte Fehler oder Dinge, die Sie verpasst haben (anstatt nur zu versuchen, auf Schlüssel zuzugreifen, die nicht vorhanden sind). .

Wie sich herausstellt (siehe die Kommentare zu Ihrer Frage), has_keyist es veraltet - also denke ich, es ist besser geschrieben als

if "blah" in B:
  A["blah"] = B["blah"]
girasquid
quelle
1

Ab Python 3.8dem Start und der Einführung von Zuweisungsausdrücken (PEP 572) ( :=Operator) können wir den Bedingungswert dictB.get('hello', None)in einer Variablen valueerfassen, um zu überprüfen, ob dies nicht der Fall ist None(da dict.get('hello', None)entweder der zugehörige Wert oder zurückgegeben wird None), und ihn dann im Hauptteil von zu verwenden die Bedingung:

# dictB = {'hello': 5, 'world': 42}
# dictA = {}
if value := dictB.get('hello', None):
  dictA["hello"] = value
# dictA is now {'hello': 5}
Xavier Guihot
quelle
Dies schlägt fehl, wenn Wert == 0
Eric
0

Obwohl die Betonung der akzeptierten Antwort auf das Prinzip "Schau, bevor du springst" für die meisten Sprachen gilt, ist möglicherweise mehr Python der erste Ansatz, der auf den Python-Prinzipien basiert. Ganz zu schweigen davon, dass es sich bei Python um einen legitimen Codierungsstil handelt. Wichtig ist, dass Sie den try-Block außer im richtigen Kontext verwenden und die Best Practices befolgen. Z.B. zu viele Dinge in einem try-Block tun, eine sehr breite Ausnahme abfangen oder schlimmer noch - die bloße Ausnahmeklausel usw.

Es ist einfacher, um Vergebung zu bitten als um Erlaubnis. (EAFP)

Siehe die Python-Dokumentreferenz hier .

Auch dieser Blog von Brett, einem der Hauptentwickler, geht kurz darauf ein.

Schaue andere SO Diskussion hier :

AJ
quelle