在讨论动态捕获异常时让我大吃一惊的是,可以让我找到隐藏的Bug和乐趣...
有问题的代码
下面的代码来自一个产品中看起来是好的抽象代码 - slightly(!) .这是调用一些统计数据的函数,然后进行处理 . 首先是用socket连接获取一个值,可能发生了socket错误.由于统计数据在系统中不是至关重要的,我们只是记一下日志错误并继续往下走.
(请注意,这篇文章我使用doctest测试的 - 这代表代码可以运行!)
04 | >>> def do_something_with_stats(stats): |
08 | ... stats = get_stats() |
09 | ... except socket.error: |
10 | ... logging.warning( "Can't get statistics" ) |
12 | ... do_something_with_stats(stats) |
查找
我们测试时并没有发现不妥, 但实际上我们注意到静态分析报告显示一个问题:
2 | filename.py:351:1: F821 undefined name 'socket' |
3 | filename.py:352:1: F821 undefined name 'logging' |
显然是我们没测试,这个问题是代码中我们没有引用socket 和 logging 两个模块.使我感到惊奇的是,这并没有预先抛出NameError错,我以为它会查找这些异常语句中的一些名词,如它需要捕捉这些异常,它需要知道些什么呢!
事实证明并非如此,异常语句的查找是延迟完成的,只是评估时抛出异常. 不只是名称延迟查找,也可以定制显示声明异常做为'参数(argument)'.
这可能是好事,坏事,或者是令人厌恶的. 好事(上段中提到的)
异常参数可以以任意形式数值传递. 这样就允许了异常的动态参数被捕获.
01 | >>> def do_something(): |
04 | >>> def attempt(action, ignore_spec): |
07 | ... except ignore_spec: |
10 | >>> attempt(do_something, ignore_spec = (NameError, TypeError)) |
11 | >>> attempt(do_something, ignore_spec = TypeError) |
12 | Traceback (most recent call last): |
14 | NameError: global name 'blob' is not defined |
坏事(上段中提到的)
这种明显的弊端就是异常参数中的错误通常只有在异常触发之后才会被注意到,不过为时已晚.当用异常去捕获不常见的事件时(例如:以写方式打开文件失败),
除非做个一个特定的测试用例,否则只有当一个异常(或者任何异常)被触发的时候才会知道, 届时记录下来并且查看是否有匹配的异常,
并且抛出它自己的错误异常 - 这是一个NameError通常所做的事情.
01 | >>> def do_something(): |
05 | ... a, b = do_something() |
|