Heim > Artikel > Backend-Entwicklung > Python fängt Ausnahmen dynamisch ab
Was mich bei der Diskussion über dynamisch abfangende Ausnahmen umgehauen hat, ist, dass ich damit versteckte Fehler finden und Spaß haben kann...
Problematischer Code
Der Der folgende Code stammt aus einem scheinbar guten abstrakten Code in einem Produkt – leicht (!) Dies ist eine Funktion, die einige statistische Daten aufruft und diese dann verarbeitet. Die erste besteht darin, einen Wert über eine Socket-Verbindung abzurufen aufgetreten sind. Da Statistiken im System nicht kritisch sind, protokollieren wir einfach die Fehler und fahren fort
(Bitte beachten Sie, dass ich diesen Artikel mit doctest getestet habe – das bedeutet, dass der Code funktioniert!)
>>> def get_stats():
... pass
...
>>> def do_something_with_stats(stats):
... pass
...
>>> try:
... stats = get_stats()
.. .error:
... logging.warning("Statistiken können nicht abgerufen werden")
... else:
... do_something_with_stats(stats)
Suchen
Wir haben beim Testen nichts Falsches gefunden, aber wir haben tatsächlich festgestellt, dass der statische Analysebericht ein Problem zeigte:
$ flake8 filename.py
filename.py:351:1: F821 undefinierter Name 'socket'
filename.py:352:1: F821 undefinierter Name 'logging'
Offensichtlich haben wir es nicht getestet. Das Problem ist, dass wir im Code nicht auf die Socket- und Protokollierungsmodule verwiesen haben. Was mich überrascht hat, war, dass dies im Voraus keinen NameError auslöste Ausnahmeanweisungen. Substantiv: Was muss es wissen, wenn es diese Ausnahmen abfangen muss? Wird nur beim Auswerten ausgelöst, sondern auch bei der expliziten Deklaration von Ausnahmen
Dies kann eine gute Sache, eine schlechte Sache oder ein Ärgernis sein >
Eine gute Sache (im vorherigen Absatz erwähnt)Ausnahmeparameter können in beliebiger Form numerisch übergeben werden. Dadurch können die dynamischen Parameter der Ausnahme erfasst werden.>> > def do_something():... blob...>>> def try(action,ignore_spec):.. . try:... action()... außerignore_spec:... pass...>>>Versuch(do_something,ignore_spec=(NameError, TypeError))>>>Versuch(do_something,ignore_spec=TypeError)Traceback (letzter Aufruf zuletzt): ...NameError: globaler Name 'blob' ist nicht definiertSchlechtes Ding (im vorherigen Absatz erwähnt)Der offensichtliche Nachteil ist, dass Fehler auftreten In-Ausnahmeparameter werden normalerweise erst bemerkt, nachdem die Ausnahme ausgelöst wurde. Wenn Sie jedoch Ausnahmen verwenden, um ungewöhnliche Ereignisse abzufangen (z. B. Fehler beim Öffnen einer Datei zum Schreiben), wird dies nur dann der Fall sein, wenn Sie einen bestimmten Testfall erstellen passieren, wenn eine Ausnahme (oder eine beliebige Ausnahme) ausgelöst wird, dann protokollieren und prüfen, ob es eine entsprechende Ausnahme gibt, und eine eigene Fehlerausnahme auslösen – das ist es, was ein NameError normalerweise tut def do_something():... return 1, 2...>>> try:... a, b = do_something()... außer ValuError: # ups - jemand kann nicht tippen... print("Ups")... else: ... print("OK!") # wir sind 'ok', bis do_something ein Triple zurückgibt...OK!Ärgerlich (im vorherigen Artikel erwähnt). Absatz)>>> versuche:... TypeError = ZeroDivisionError # warum sollten wir das jetzt tun...?!... 1 / 0.. außer TypeError:... print("Gefangen!")... else:... print(" ok")
...Abgefangen!Nicht nur Ausnahmeparameter werden nach Namen gesucht, auch andere Ausdrücke funktionieren so: >>> Versuchen Sie: ... 1 / 0... außer eval(''.join('Zero Division Error'.split())):... print("Gefangen!")... else:... print("ok")...
Abgefangen! Ausnahmeparameter können nicht nur zur Laufzeit ermittelt werden, es können sogar Ausnahmeinformationen während des Lebenszyklus verwendet werden. Das Folgende ist eine kompliziertere Methode, um ausgelöste Ausnahmen abzufangen – aber das ist alles 🎜>
>>> import sys
>>> def current_exc_type():
... return sys.exc_info()[0]
.. .
>>> try:
... blob
... außer current_exc_type():
... print (" Verstanden !")
...
Verstanden!
Offensichtlich ist es das, wonach wir wirklich suchen. Wenn wir Ausnahmehandler schreiben, sollten wir zuerst das tun, was uns in den Sinn kommt ist dieser
(Byte-)Code
Um zu bestätigen, wie es bei der Ausnahmebehandlung funktioniert, habe ich dis.dis() für ein Ausnahmebeispiel ausgeführt ( Beachten Sie, dass die Zerlegung hier unter Python 2.7 erfolgt. Unter Python 3.3 wird ein anderer Bytecode generiert, der aber grundsätzlich ähnlich ist):
>>> import dis
> >> def x():
... versuchen :
... bestanden
... außer Blobbity:
... print ("schlecht")
... else:
... print("good")
...
>>> dis.dis(x) # doctest: NORMALIZE_WHITESPACE
2 0 SETUP_EXCEPT 4 (bis 7)
3 3 POP_BLOCK
4 JUMP_FORWARD 22 (bis 29)
4 >> 7 DUP_TOP
8 LOAD_GLOBAL 0 (Blobbity)
11 COMPARE_OP 10 (Ausnahmeübereinstimmung)
14 POP_JUMP_IF_FALSE 28
17 POP_TOP
18 POP_TOP
19 POP_TOP
5 20 LOAD_CONST 1 ('schlecht')
23 PRINT_ITEM
24 PRINT_NEWLINE
25 JUMP_FORWARD 6 (bis 34)
>> 28 END_FINALLY
7 >> 29 LOAD_CONST 2 ('gut')
32 PRINT_ITEM
33 PRINT_NEWLINE
> > 34 LOAD_CONST 0 (Keine)
37 RETURN_VALUE
这显示出了我原来预期的问题(Problem). 异常处理"看起来"完全是按照Python内部机制在运行. 并且如果没有异常抛出它们将被完全忽略了.SETUP_EXCEPT并不关心发生了什么, 仅仅是如果发生了异常, 第一个处理程序应该被评估,然后第二个,以此类推.
每个处理程序都有两部分组成: 获得一个异常的规则, 和刚刚抛出的异常进行对比. 一切都是延迟的, 一切看起来正如对你的逐行的代码的预期一样, 从解释器的角度来考虑.
总结
虽然这种动态的异常参数让我大吃一惊, 但是这当中包含很多有趣的应用. 当然去实现它们当中的许多或许是个馊主意,呵呵
有时并不能总是凭直觉来确认有多少Python 特性的支持 - 例如 在类作用域内 表达式和声明都是被显式接受的, (而不是函数, 方法, 全局作用域),但是并不是所有的都是如此灵活的. 虽然(我认为)那将是十分美好的, 表达式被禁止应用于装饰器 - 以下是Python语法错误:
@(lamb da fn: fn)
def x() :
pass
这个是尝试动态异常参数通过给定类型传递给第一个异常的例子, 静静的忍受重复的异常:
>>> class Pushover(object):
... exc_spec = set()
...
... def try(self, action):
... try:
... return action()
... außer tuple(self.exc_spec):
... pass
... außer BaseException als e:
... self.exc_spec.add(e.__class__)
... raise
...
>>> pushover = Pushover()
>>>
>>> für _ in range(4):
... versuche:
... pushover.attempt(lambda: 1 / 0)
... außer:
... print ("Boo")
... else:
... print ("Yay!")
Boo
Yay!
Yay!
Yay!