在讨论动态捕获异常时让我大吃一惊的是,可以让我找到隐藏的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
这显示出了我原来预期的问题(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!
www.qytang.com/
http://www.qytang.com/cn/list/29/
http://www.qytang.com/cn/list/28/358.htm
http://www.qytang.com/cn/list/41/
http://www.qytang.com/cn/list/37/
http://www.qytang.com/cn/list/46/
http://www.qytang.com/cn/page/19.htm
http://www.qytang.com/cn/list/32/
http://www.qytang.com/cn/list/28/
http://www.qytang.com/cn/list/25/
http://www.qytang.com/cn/list/28/625.htm
http://www.qytang.com/cn/list/28/612.htm
http://www.qytang.com/cn/list/28/611.htm
python动态捕获异常-乾颐堂的更多相关文章
-
常用的 Python 调试工具,Python开发必读-乾颐堂
以下是我做调试或分析时用过的工具的一个概览.如果你知道有更好的工具,请在评论中留言,可以不用很完整的介绍. 日志 没错,就是日志.再多强调在你的应用里保留足量的日志的重要性也不为过.你应当对重要的内容 ...
-
python性能测试脚本-乾颐堂
废话不多说,直接上代码. import httplib import urllib import time import json class Transaction(object): ...
-
乾颐堂7月HCIE、CCIE通过名单
拼多多都上市了,现在很多培训机构也流行公用一张PASS了,山寨总是山寨的,不脚踏实地总是欺骗自己7月(自然月)乾颐堂通过22名学员,每个考试日通过一名HCIE.CCIE 转载于:https://blo ...
-
python生成验证码,文字转换为图片-乾颐堂
在58或者赶集等一些网站上经常看到手机号是图片格式,或者一些网站的验证码.这些都是动态生成的,今天我们来看一下如何用python把文字生成图片.其实今天主要借助pygame的图像渲染模块,这样比较简单 ...
-
Python使用wxPython、py2exe编写桌面程序-乾颐堂
Python是支持可视化编程,即编写gui程序,你可以用它来编写自己喜欢的桌面程序.使用wxPython来做界面非常的简单,只是不能像C#一样拖动控件,需要自行写代码布局.在完成编写之后,由于直接的p ...
-
python使用wmi模块获取windows下的系统信息监控系统-乾颐堂
Python用WMI模块获取Windows系统的硬件信息:硬盘分区.使用情况,内存大小,CPU型号,当前运行的进程,自启动程序及位置,系统的版本等信息. 本文实例讲述了python使用wmi模块获取w ...
-
Python图像处理库:Pillow 初级教程-乾颐堂
Image类 Pillow中最重要的类就是Image,该类存在于同名的模块中.可以通过以下几种方式实例化:从文件中读取图片,处理其他图片得到,或者直接创建一个图片. 使用Image模块中的open函数 ...
-
python的metaclass浅析-乾颐堂
元类一般用于创建类.在执行类定义时,解释器必须要知道这个类的正确的元类.解释器会先寻找类属性__metaclass__,如果此属性存在,就将这个属性赋值给此类作为它的元类.如果此属性没有定义,它会向上 ...
-
Python和JavaScript间代码转换4个工具-乾颐堂
Python 还是 JavaScript?虽然不少朋友还在争论二者目前谁更强势.谁又拥有着更为光明的发展前景,但毫无疑问,二者的竞争在 Web 前端领域已经拥有明确的答案.立足于浏览器平台,如果放弃 ...
随机推荐
-
SQL like 模糊查询
SQL 模糊查询 在进行数据库查询时,有完整查询和模糊查询之分. 一般模糊查询语句如下: SELECT 字段 FROM 表 WHERE 某字段 Like 条件 其中关于条件,SQL提供了四种匹配模式: ...
-
url匹配和match()方法
下面的全局匹配可以找到字符串中的所有数字: "1 plus 2 equals 3".match(/\d+/g) // 返回 ["1", "2" ...
-
c语言_判断例子
例一: #include "stdio.h" int main() { ; if(i) printf("hi"); if(!i) printf("hi ...
-
查看百度CUID方法
百度地图CUID号获取方法:安卓系统百度地图APP→“设置”→ 长按“关于”
-
Go Revel - Websockets
revel提供了对`Websockets`的支持. 处理`Websockets`链接: 1.添加一个`WS`类型方法的路由 2.添加一个action接受 `*websocket.Conn`参数 例如, ...
-
【PTA 天梯赛】L3-002 特殊堆栈(二分)
堆栈是一种经典的后进先出的线性结构,相关的操作主要有“入栈”(在堆栈顶插入一个元素)和“出栈”(将栈顶元素返回并从堆栈中删除).本题要求你实现另一个附加的操作:“取中值”——即返回所有堆栈中元素键值的 ...
-
pipreqs
安装:pip3 install pipreqs 作用:帮你检测当前程序所有的安装模块,并输入到requirements.txt 执行:pipreqs ./ (必须在当前程序目录下) pycharm会 ...
-
nginx.conf常用配置解析
一.全局配置 user username groupname 运行用户及用户组 worker_processes auto 启动进程,通常设置成和cpu数量相等的一个数值,默认为1.可以设置为auto ...
-
LeetCode——Reverse String
LeetCode--Reverse String Question Write a function that takes a string as input and returns the stri ...
-
luogu2948 滑雪课
题解里面全是dp的大神本蒟蒻瑟瑟发抖奉上一篇记忆化搜索... 其实嘛,记忆化搜索还是很安全透彻清真人品的,一般递推不好实现dp可以用记忆化搜索 然后本题先预处理一个mint[i]代表当前能力值为i,参 ...