Python-Vergebung vs. Erlaubnis und Entenschreiben

44

In Python höre ich oft, dass es besser ist, "um Verzeihung zu bitten" (Ausnahmefang), als "um Erlaubnis zu bitten" (Typ- / Zustandsprüfung). In Bezug auf die Durchsetzung der Enten-Typisierung in Python ist dies

try:
    x = foo.bar
except AttributeError:
    pass
else:
    do(x)

besser oder schlechter als

if hasattr(foo, "bar"):
    do(foo.bar)
else:
    pass

in Bezug auf Leistung, Lesbarkeit, "Python" oder einen anderen wichtigen Faktor?

Darkfeline
quelle
17
gibt es eine dritte Option ist, tun nichts und ohne eine Bar als Bug jede foo behandeln
jk.
Ich erinnere mich, dass ich gehört habe, hasattrdass dies intern mit genau diesem Try / Catch umgesetzt wurde. Nicht sicher, ob es wahr ist ... (es würde anders auf Eigenschaften wirken, nicht wahr? Vielleicht denke ich an getattr..)
Izkata
@Izkata: Die Implementierung vonhasattr verwendet das C-API-Äquivalent von getattr(return, Truewenn erfolgreich, Falsewenn nicht), aber die Behandlung von Ausnahmen in C ist viel schneller.
Martijn Pieters
2
Ich habe Martijns Antwort akzeptiert, möchte aber hinzufügen, dass Sie beim Festlegen eines Attributs auf jeden Fall die Verwendung von try / catch in Betracht ziehen sollten, da es sich möglicherweise um eine Eigenschaft ohne Setter handelt. In diesem Fall ist hasattr wahr , aber es wird trotzdem AttributeError auslösen.
Darkfeline

Antworten:

60

Es hängt wirklich davon ab, wie oft Sie denken, dass die Ausnahme ausgelöst wird.

Beide Ansätze sind meiner Meinung nach gleichermaßen gültig, zumindest in Bezug auf Lesbarkeit und Pythonität. Wenn jedoch 90% Ihrer Objekte nicht über das Attribut verfügen, stellen barSie einen deutlichen Leistungsunterschied zwischen den beiden Ansätzen fest:

>>> import timeit
>>> def askforgiveness(foo=object()):
...     try:
...         x = foo.bar
...     except AttributeError:
...         pass
... 
>>> def askpermission(foo=object()):
...     if hasattr(foo, 'bar'):
...         x = foo.bar
... 
>>> timeit.timeit('testfunc()', 'from __main__ import askforgiveness as testfunc')
2.9459929466247559
>>> timeit.timeit('testfunc()', 'from __main__ import askpermission as testfunc')
1.0396890640258789

Aber wenn 90% der Objekte haben das Attribut haben, haben das Blatt gewendet worden:

>>> class Foo(object):
...     bar = None
... 
>>> foo = Foo()
>>> timeit.timeit('testfunc(foo)', 'from __main__ import askforgiveness as testfunc, foo')
0.31336188316345215
>>> timeit.timeit('testfunc(foo)', 'from __main__ import askpermission as testfunc, foo')
0.4864199161529541

Aus Sicht der Leistung müssen Sie also den Ansatz auswählen, der für Ihre Umstände am besten geeignet ist.

Am Ende kann eine strategische Verwendung des timeitModuls das Pythonischste sein, was Sie tun können.

Martijn Pieters
quelle
1
Vor einigen Wochen habe ich folgende Frage gestellt: programmers.stackexchange.com/questions/161798/… Dort habe ich gefragt, ob Sie in lose getippten Sprachen zusätzlich Typprüfungen durchführen müssen, und ich wurde von Leuten bombardiert, die sagten, Sie hätten es nicht getan. Weiß, ich sehe, dass du hast.
Tulains Córdova
@ user1598390: Wenn Sie eine API definieren, die eine homogene Mischung von Typen erwartet , müssen Sie einige Tests durchführen. Meistens nicht. Dies ist ein spezifischer Bereich, aus dem Sie leider keine Regeln für Paradigmen als Ganzes ableiten können.
Martijn Pieters
Bei jeder ernsthaften Systementwicklung muss eine API definiert werden. Ich denke, dafür sind typengerechte Sprachen am besten geeignet, da Sie weniger Code benötigen, da der Compiler die Typen in der Kompilierungszeit für Sie überprüft.
Tulains Córdova
1
@GarethRees: Es wird ein Muster für die zweite Hälfte der Antwort erstellt, in dem ich ein Argument an die getestete Funktion weitergebe.
Martijn Pieters
1
Beachten Sie, dass hasattres tatsächlich das C-api-Äquivalent eines try-except unter der Haube gibt, da es sich als die einzige allgemeine Möglichkeit herausstellt, zu bestimmen, ob ein Objekt in Python ein Attribut hat, zu versuchen, darauf zuzugreifen.
user2357112
11

In Python erhalten Sie häufig eine bessere Leistung, wenn Sie Dinge auf die Python-Weise erledigen. In anderen Sprachen wird die Verwendung von Ausnahmen zur Flusskontrolle im Allgemeinen als schrecklich angesehen, da Ausnahmen normalerweise einen außerordentlichen Aufwand verursachen. Da diese Technik jedoch ausdrücklich in Python unterstützt wird, ist der Interpreter für diesen Codetyp optimiert.

Wie bei allen Leistungsfragen besteht die einzige Möglichkeit, sicher zu sein, darin, Ihren Code zu profilieren. Schreiben Sie beide Versionen und sehen Sie, welche schneller läuft. Meiner Erfahrung nach ist der "Python-Weg" in der Regel der schnellste Weg.

tylerl
quelle
3

Die Leistung ist meines Erachtens ein zweitrangiges Anliegen. In diesem Fall hilft Ihnen ein Profiler dabei, sich auf die tatsächlichen Engpässe zu konzentrieren, die möglicherweise darin bestehen, wie Sie mit möglichen illegalen Argumenten umgehen.

Lesbarkeit und Einfachheit stehen dagegen immer im Vordergrund. Hier gibt es keine strengen Regeln, nutzen Sie einfach Ihr Urteilsvermögen.

Dies ist eine universelle Angelegenheit, aber umwelt- oder sprachspezifische Konventionen sind relevant. Zum Beispiel ist es in Python normalerweise in Ordnung, einfach das erwartete Attribut zu verwenden und einen möglichen AttributeError den Aufrufer erreichen zu lassen.

Orip
quelle
-1

In Bezug auf die Korrektheit denke ich, dass die Ausnahmebehandlung der richtige Weg ist (manchmal benutze ich jedoch den hasattr () - Ansatz). Das Grundproblem bei der Verwendung von hasattr () besteht darin, dass Verstöße gegen Codeverträge in unbeaufsichtigte Fehler umgewandelt werden (dies ist ein großes Problem in JavaScript, bei dem nicht vorhandene Eigenschaften nicht berücksichtigt werden).

Joe
quelle
3
Ihre Antwort scheint nicht viel mehr hinzuzufügen, als andere bereits angegeben haben. Im Gegensatz zu anderen Sites erwarten Programmierer Antworten, in denen das Warum hinter der Antwort erklärt wird. Sie sprechen einen guten Punkt mit der Frage des stillen Scheiterns an, müssen dem aber nicht gerecht werden. Das Lesen der folgenden
Die letzte Antwort, die ich machte, wurde als zu umfassend kritisiert. Ich dachte, ich würde es kurz und prägnant versuchen.
Joe