Python - "Wenn Foo in Dikt" gegen "Versuch: Dikt [Foo]"

24

Dies ist weniger eine Frage der Art der Ententypisierung als vielmehr der Frage, ob man pythonisch bleibt, nehme ich an.

Zuallererst - wenn es um Dikte geht, insbesondere wenn die Struktur des Diktats ziemlich vorhersehbar ist und ein bestimmter Schlüssel normalerweise nicht vorhanden ist, manchmal aber vorhanden ist, denke ich zuerst an zwei Ansätze:

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

Und natürlich Ye Olde 'Vergebung gegen Erlaubnis' Ansatz.

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

Als ein geselliger Python-Typ habe ich das Gefühl, dass ich sehe, dass Letzterer viel bevorzugt wird, was sich wohl nur seltsam anfühlt, weil die Python-Dokumente try/exceptsbevorzugt zu sein scheinen, wenn ein tatsächlicher Fehler vorliegt, im Gegensatz zu einem, ähm, fehlenden Erfolg?

Wenn das gelegentliche Diktat keinen Schlüssel in myDict hat und bekannt ist , dass es diesen Schlüssel nicht immer hat, ist ein Versuch / außer in Bezug auf den Kontext irreführend? Dies ist kein Programmierfehler, es ist nur eine Tatsache der Daten - dieses Diktat hatte nur diesen bestimmten Schlüssel nicht.

Dies scheint besonders wichtig zu sein, wenn Sie sich die try / except / else-Syntax ansehen, die sehr nützlich ist, um sicherzustellen, dass try nicht zu viele Fehler abfängt. Sie können Folgendes tun:

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

Wird das nicht dazu führen, dass alle möglichen seltsamen Fehler verschluckt werden, die wahrscheinlich auf einen schlechten Code zurückzuführen sind? Der obige Code verhindert möglicherweise, dass Sie sehen, dass Sie versuchen, etwas hinzuzufügen, 2 + {}und Sie werden möglicherweise nie bemerken, dass ein Teil Ihres Codes fürchterlich schief gelaufen ist. Ich schlage nicht vor, dass wir All The Types überprüfen, deshalb ist es Python und nicht JavaScript - aber auch im Kontext von try / except scheint es so, als ob es das Programm dazu bringen soll, etwas zu tun, was es nicht tun sollte, stattdessen zu ermöglichen, weiterzumachen.

Mir ist klar, dass das obige Beispiel so etwas wie ein Strohmann-Argument ist und tatsächlich absichtlich schlecht ist. Aber angesichts des pythonischen Glaubens better to ask forgiveness than permissionkann ich nicht anders, als das Gefühl zu haben, dass sich die Frage stellt, wo die Grenze zwischen der korrekten Anwendung von if / else und try / except liegt, insbesondere wenn Sie wissen, was Sie erwartet die Daten, mit denen Sie arbeiten.

Ich spreche hier nicht einmal von Geschwindigkeitsproblemen oder Best Practices. Ich bin nur ein bisschen verwirrt von dem Venn-Diagramm, in dem es so aussieht, als ob es in beide Richtungen gehen könnte, aber die Leute irren sich auf der Seite eines Versuchs / einer Ausnahme weil 'irgendwo jemand gesagt hat, es sei Pythonic'. Habe ich falsche Schlussfolgerungen bezüglich der Anwendung dieser Syntax gezogen?


quelle
Ich bin damit einverstanden, dass die Tendenz zum Ausprobieren das Risiko birgt, Fehler zu verbergen. Meiner Meinung nach sollten Sie im Allgemeinen keine Versuche durchführen - mit Ausnahme der Bedingungen, die Sie erwarten (für diese Regel gelten natürlich einige Ausnahmen).
Gordon Bean

Antworten:

26

Verwenden Sie stattdessen die Methode get :

some_dict.get(the_key, default_value)

... wo default_valuewird der Wert zurückgegeben, wenn the_keynicht in some_dict. Wenn Sie weglassen default_value, Nonewird zurückgegeben, wenn der Schlüssel fehlt.

In der Regel bevorzugen die Benutzer in Python eher try / except als zuerst etwas zu überprüfen - siehe den EAFP- Eintrag im Glossar . Beachten Sie, dass viele Funktionen zum Testen der Mitgliedschaft Ausnahmen hinter den Kulissen verwenden.

tückisch
quelle
Ist das nicht dasselbe Problem? Wenn wir versuchen, mit einem zu arbeiten dictund die Arbeit auf der Grundlage der gefundenen if myDict.get(a_key) is not None: do_work(myDict[a_key])Informationen zu erledigen , fühlt es sich wortreicher an und ist ein weiteres Beispiel für implizites Hinschauen vor dem Sprung. Außerdem ist es meiner Meinung nach etwas weniger lesbar. Vielleicht verstehe ich das nicht dict.get(a_key, a_default)im richtigen Kontext, aber es scheint ein Vorläufer für das Schreiben von 'not-a-switch-statement if / elses' oder magischen Werten zu sein. Ich mag, was diese Methode tut, ich werde es tiefer untersuchen.
1
@Stick - Sie tun:, data = myDict.get(a_key, default)und entweder do_worktun Sie das Richtige, wenn es Ihnen gegeben wird default(z. B. None), oder Sie tun es if data: do_work(data). In beiden Fällen ist nur eine Suche erforderlich. ( if data is not None: ...In beiden Fällen ist es möglicherweise immer noch nur eine Suche, und dann kann Ihre eigene Logik die
Kontrolle
1
Daher habe ich beschlossen, dass mir dict.get () in meinem aktuellen Projekt eine Menge Mühe erspart hat. Es hat meine Zeilenanzahl reduziert, viel schrecklich aussehenden try/except/elseMüll beseitigt und meiner Meinung nach die Lesbarkeit meines Codes insgesamt verbessert. Ich habe eine wertvolle Lektion gelernt, die ich zuvor gehört hatte, die ich aber vernachlässigt habe: "Wenn Sie das Gefühl haben, zu viel Code zu schreiben, hören Sie auf und schauen Sie sich die Sprachfunktionen an." Vielen Dank!!
@Stick - freut mich zu hören :) Ich mag diesen Rat, ich habe es noch nie gehört.
Detly
1
Welches ist technisch gesehen das schnellste?
Olivier Pons
4

Ist ein fehlender Schlüssel eine Regel oder eine Ausnahme ?

Aus pragmatischer Sicht ist das Werfen / Fangen viel langsamer als das Überprüfen, ob der Schlüssel in der Tabelle ist. Wenn das Fehlen eines Schlüssels häufig vorkommt, sollten Sie die Bedingung verwenden.

Ein weiterer Vorteil von condition ist eine else- Klausel - es ist viel einfacher zu verstehen, wann ein Block ausgeführt wird, wenn berücksichtigt wird, dass dieselbe Ausnahmeklasse aus mehreren Anweisungen im try-Block geworfen werden kann.

Der Code in der except- Klausel sollte nicht Teil des normalen Fehlers sein (z. B. wenn der Schlüssel nicht vorhanden ist, fügen Sie ihn hinzu) - er sollte den Fehler behandeln.

Eugene
quelle
4
"Aus pragmatischer Sicht ist das Werfen / Fangen viel langsamer als das Überprüfen, ob der Schlüssel in der Tabelle ist" - nein, ist es nicht. Zumindest nicht unbedingt. Zuletzt habe ich überprüft, dass die gängigsten Python-Implementierungen die try/ catch-Version schneller ausführen.
Detly
2
Throw / Catch in Python ist in Bezug auf die Leistung nicht allzu groß, in dem Maße, wie es in Python häufig zur Flusskontrolle verwendet wird. Darüber hinaus besteht die Möglichkeit, dass dieses Diktat in bestimmten Situationen zwischen dem Test und dem Zugriff geändert wird.
Whatsisname
@whatsisname - Ich habe darüber nachgedacht, dies zu meiner Antwort hinzuzufügen, aber TBH: Wenn Sie solche
Rennrisiken
1

Im Geiste des "pythonischen" und insbesondere des "Enten-Tippens" erscheint mir das try / except fast zerbrechlich.

Alles, was Sie wirklich wollen, ist etwas mit diktatartigem Zugriff, das jedoch __getitem__überschrieben werden kann, um etwas zu tun, das nicht ganz Ihren Erwartungen entspricht. Beispiel defaultdictaus der Standardbibliothek:

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

Da der Standard-Rückgabewert Falsy ist, funktioniert eine solche Zugriffskontrolle die meiste Zeit einwandfrei. Der Code scheint auch einfacher zu sein, weshalb meine Mitarbeiter und ich das seit Monaten getan haben.

Leider führten diese zusätzlichen Schlüssel einige Monate später tatsächlich zu Problemen und schwer auffindbaren Fehlern, da sie 0zu einem gültigen Wert wurden. Und manchmal wir würden verwenden inum zu überprüfen , ob ein Schlüssel vorhanden, so dass der Code verschiedene Dinge tun , wenn es identisch sein sollte.

Ich schlage vor, dass es kaputt aussieht, weil in Pythons Geist des Entenschreibens alles, was Sie wollen sollten, etwas Diktatartiges ist und defaultdictgenau zu dieser Anforderung passt, wie jedes Objekt, von dem Sie möglicherweise erben dict. Sie können nicht garantieren, dass der Anrufer die Implementierung später nicht ändern wird, also sollten Sie das mit den geringsten Nebenwirkungen tun.

Verwenden Sie die erste Version if myKey in mydict.


Beachten Sie auch, dass es sich hierbei speziell um das Beispiel in der Frage handelt. Das Python-Credo "Besser um Vergebung als um Erlaubnis bitten" soll vor allem sicherstellen, dass Sie tatsächlich richtig handeln, wenn Sie nicht das bekommen, was Sie wollen. Wenn Sie beispielsweise prüfen, ob eine Datei vorhanden ist, und dann versuchen, sie zu lesen, können mindestens drei Dinge schief gehen:

  • Eine Rennbedingung ist, dass die Datei möglicherweise gelöscht / verschoben / etc. Wurde
  • Sie haben keine Leserechte für die Datei
  • Die Datei wurde beschädigt

Das erste, bei dem Sie versuchen können, "um Erlaubnis zu bitten", aber aufgrund der Rennbedingungen würde es nicht immer funktionieren. der zweite ist zu leicht zu vergessen zu überprüfen; und die dritte kann nicht überprüft werden, ohne zu versuchen, die Datei zu lesen. In jedem Fall wird das, was Sie nach dem Fehlschlagen tun, mit ziemlicher Sicherheit das gleiche sein. In diesem Beispiel ist es daher besser, mit try / except um Verzeihung zu bitten.

Izkata
quelle