python动态捕捉异常
在接头动态捕捉异常时让我大吃一惊的是,可以让我找到埋没的Bug和兴趣…
有问题的代码
下面的代码来自一个产物中看起来是好的抽象代码 – slightly(!) .这是挪用一些统计数据的函数,然后举办处理惩罚 . 首先是用socket毗连获取一个值,大概产生了socket错误.由于统计数据在系统中不是至关重要的,我们只是记一下日志错误并继承往下走.
(请留意,这篇文章我利用doctest测试的 – 这代表代码可以运行!)
>>> def get_stats():
… pass
…
>>> def do_something_with_stats(stats):
… pass
…
>>> try:
… stats = get_stats()
… except socket.error:
… logging.warning("Can't get statistics")
… else:
… do_something_with_stats(stats)
查找
我们测试时并没有发明不当, 但实际上我们留意到静态阐明陈诉显示一个问题:
$ flake8 filename.py
filename.py:351:1: F821 undefined name 'socket'
filename.py:352:1: F821 undefined name 'logging'
显然是我们没测试,这个问题是代码中我们没有引用socket 和 logging 两个模块.使我感想诧异的是,这并没有预先抛出NameError错,我觉得它会查找这些异常语句中的一些名词,如它需要捕获这些异常,它需要知道些什么呢!
事实证明并非如此,异常语句的查找是延迟完成的,只是评估时抛出异常. 不可是名称延迟查找,也可以定制显示声明异常做为'参数(argument)'.
这大概是功德,坏事,可能是令人厌恶的.
功德(上段中提到的)
异常参数可以以任意形式数值通报. 这样就答允了异常的动态参数被捕捉.
>>> def do_something():
… blob
…
>>> def attempt(action, ignore_spec):
… try:
… action()
… except ignore_spec:
… pass
…
>>> attempt(do_something, ignore_spec=(NameError, TypeError))
>>> attempt(do_something, ignore_spec=TypeError)
Traceback (most recent call last):
…
NameError: global name 'blob' is not defined
坏事(上段中提到的)
这种明明的漏洞就是异常参数中的错误凡是只有在异常触发之后才会被留意到,不外为时已晚.当用异常去捕捉不常见的事件时(譬喻:以写方法打开文件失败),除非做个一个特定的测试用例,不然只有当一个异常(可能任何异常)被触发的时候才会知道, 届时记录下来而且查察是否有匹配的异常, 而且抛出它本身的错误异常 – 这是一个NameError凡是所做的工作.
>>> def do_something():
… return 1, 2
…
>>> try:
… a, b = do_something()
… except ValuError: # oops – someone can't type
… print("Oops")
… else:
… print("OK!") # we are 'ok' until do_something returns a triple…
OK!
令人讨厌的(上段中提到的)
>>> try:
… TypeError = ZeroDivisionError # now why would we do this…?!
… 1 / 0
… except TypeError:
… print("Caught!")
… else:
… print("ok")
…
Caught!
不只仅是异常参数通过名称查找, – 其它的表达式也是这样事情的:
>>> try:
… 1 / 0
… except eval(''.join('Zero Division Error'.split())):
… print("Caught!")
… else:
… print("ok")
…
Caught!
异常参数不只仅只能在运行时确定,它甚至可以利用在生命周期内的异常的信息. 以下是一个较量费解的方法来捕获抛出的异常 – 但也只能如此了:
>>> import sys
>>> def current_exc_type():
… return sys.exc_info()[0]
…
>>> try:
… blob
… except current_exc_type():
… print ("Got you!")
…
Got you!
很明明这才是我们真正要寻找的当我们写异常处理惩罚措施时, 我们应该首先想到的就是这种
(字节)代码
为了确认它是如安在异常处理惩罚事情中呈现的,我在一个异常的例子中运行 dis.dis(). (留意 这里的解析是在Python2.7 下 – 差异的字节码是Python 3.3下发生的,但这根基上是雷同的):
>>> import dis
>>> def x():
… try:
… pass
… except Blobbity:
… print("bad")
… else:
… print("good")
…
>>> dis.dis(x) # doctest: +NORMALIZE_WHITESPACE
2 0 SETUP_EXCEPT 4 (to 7)
<BLANKLINE>
3 3 POP_BLOCK
4 JUMP_FORWARD 22 (to 29)
<BLANKLINE>
4 >> 7 DUP_TOP
8 LOAD_GLOBAL 0 (Blobbity)
11 COMPARE_OP 10 (exception match)
14 POP_JUMP_IF_FALSE 28
17 POP_TOP
18 POP_TOP
19 POP_TOP
<BLANKLINE>
5 20 LOAD_CONST 1 ('bad')
23 PRINT_ITEM
24 PRINT_NEWLINE
25 JUMP_FORWARD 6 (to 34)
>> 28 END_FINALLY
<BLANKLINE>
7 >> 29 LOAD_CONST 2 ('good')
32 PRINT_ITEM
33 PRINT_NEWLINE
>> 34 LOAD_CONST 0 (None)
37 RETURN_VALUE
#p#分页标题#e#
这显示出了我本来预期的问题(issue). 异常处理惩罚"看起来"完全是凭据Python内部机制在运行. 这一步完全没有须要知道关于后续的异常“捕捉”语句, 而且假如没有异常抛出它们将被完全忽略了.SETUP_EXCEPT并不体贴产生了什么, 仅仅是假如产生了异常, 第一个处理惩罚措施应该被评估,然后第二个,以此类推.
每个处理惩罚措施都有两部门构成: 得到一个异常的法则, 和方才抛出的异常举办比拟. 一切都是延迟的, 一切看起来正如对你的逐行的代码的预期一样, 从表明器的角度来思量. 没有任务智慧的工作产生了,只是溘然使得它看起来很是智慧.
总结
固然这种动态的异常参数让我大吃一惊, 可是这傍边包括许多有趣的应用. 虽然去实现它们傍边的很多或者是个馊主意,呵呵
有时并不能老是凭直觉来确认有几多Python特性的支持 – 譬喻 在类浸染域内 表达式和声明都是被显式接管的, (而不是函数, 要领, 全局浸染域),可是并不是所有的都是如此机动的. 固然(我认为)那将是十分优美的, 表达式被克制应用于装饰器 – 以下是Python语法错误:
@(lambda fn: fn)
def x():
pass
这个是实验动态异常参数通过给定范例通报给第一个异常的例子, 悄悄的忍受反复的异常:
>>> class Pushover(object):
… exc_spec = set()
…
… def attempt(self, action):
… try:
… return action()
… except tuple(self.exc_spec):
… pass
… except BaseException as e:
… self.exc_spec.add(e.__class__)
… raise
…
>>> pushover = Pushover()
>>>
>>> for _ in range(4):
… try:
… pushover.attempt(lambda: 1 / 0)
… except:
… print ("Boo")
… else:
… print ("Yay!")
Boo
Yay!
Yay!
Yay!