在讨论动态捕获异常时让我大吃一惊的是,可以让我找到隐藏的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动态捕获异常-乾颐堂的更多相关文章

  1. 常用的 Python 调试工具,Python开发必读-乾颐堂

    以下是我做调试或分析时用过的工具的一个概览.如果你知道有更好的工具,请在评论中留言,可以不用很完整的介绍. 日志 没错,就是日志.再多强调在你的应用里保留足量的日志的重要性也不为过.你应当对重要的内容 ...

  2. python性能测试脚本-乾颐堂

    废话不多说,直接上代码. import httplib import urllib import time import json     class Transaction(object):     ...

  3. 乾颐堂7月HCIE、CCIE通过名单

    拼多多都上市了,现在很多培训机构也流行公用一张PASS了,山寨总是山寨的,不脚踏实地总是欺骗自己7月(自然月)乾颐堂通过22名学员,每个考试日通过一名HCIE.CCIE 转载于:https://blo ...

  4. python生成验证码,文字转换为图片-乾颐堂

    在58或者赶集等一些网站上经常看到手机号是图片格式,或者一些网站的验证码.这些都是动态生成的,今天我们来看一下如何用python把文字生成图片.其实今天主要借助pygame的图像渲染模块,这样比较简单 ...

  5. Python使用wxPython、py2exe编写桌面程序-乾颐堂

    Python是支持可视化编程,即编写gui程序,你可以用它来编写自己喜欢的桌面程序.使用wxPython来做界面非常的简单,只是不能像C#一样拖动控件,需要自行写代码布局.在完成编写之后,由于直接的p ...

  6. python使用wmi模块获取windows下的系统信息监控系统-乾颐堂

    Python用WMI模块获取Windows系统的硬件信息:硬盘分区.使用情况,内存大小,CPU型号,当前运行的进程,自启动程序及位置,系统的版本等信息. 本文实例讲述了python使用wmi模块获取w ...

  7. Python图像处理库:Pillow 初级教程-乾颐堂

    Image类 Pillow中最重要的类就是Image,该类存在于同名的模块中.可以通过以下几种方式实例化:从文件中读取图片,处理其他图片得到,或者直接创建一个图片. 使用Image模块中的open函数 ...

  8. python的metaclass浅析-乾颐堂

    元类一般用于创建类.在执行类定义时,解释器必须要知道这个类的正确的元类.解释器会先寻找类属性__metaclass__,如果此属性存在,就将这个属性赋值给此类作为它的元类.如果此属性没有定义,它会向上 ...

  9. Python和JavaScript间代码转换4个工具-乾颐堂

    Python 还是 JavaScript?虽然不少朋友还在争论二者目前谁更强势.谁又拥有着更为光明的发展前景,但毫无疑问,二者的竞争在 Web 前端领域已经拥有明确的答案.立足于浏览器平台,如果放弃 ...

随机推荐

  1. 《DSP using MATLAB》示例Example 9.9

    代码: %% ------------------------------------------------------------------------ %% Output Info about ...

  2. 对Linux内核中进程上下文和中断上下文的理解

    内核空间和用户空间是操作系统理论的基础之一,即内核功能模块运行在内核空间,而应用程序运行在用户空间.现代的CPU都具有不同的操作模式,代表不同的 级别,不同的级别具有不同的功能,在较低的级别中将禁止某 ...

  3. PyCharm永久激活

    目录 windws Mac Windows下破解 激活前准备工作 激活前请先关闭pycharm 修改配置文件的时候你需要填写你的安装路径 如果出现修改配置文件后无法打开pycharm,那就移动补丁的位 ...

  4. C# Async&Await

    在async和await之前我们用Task来实现异步任务是这样做的: static Task<string> GetBaiduHtmlTAP() { //创建一个异步Task对象,内部封装 ...

  5. (转)如何实现CSS限制字数,超出部份显示点点点...

    <div style="width:200px; white-space:nowrap;overflow:hidden;text-overflow:ellipsis; border:1 ...

  6. XSS获取cookie并利用

    获取cookie利用代码cookie.asp <html> <title>xx</title> <body> <%testfile = Serve ...

  7. 使用maven构建基本的web项目结构

    由于当前公司在组织进行项目基本结构的整理,将以前通过eclipse/ ant 方式构建的项目向maven上迁移,于是便进行maven项目方面的调研. 对于maven项目,基本的结构已经在标准文件中: ...

  8. leetcode917

    class Solution { public: string reverseOnlyLetters(string S) { int len = S.length(); queue<char&g ...

  9. MySQL GTID (一)

    MySQL GTID 系列之一 一.GTID相关概念 GTID:全局事务标识符,MySQL5.6版本开始在主从复制中推出的重量级特性. 每提交一个事务,当前执行线程都会拿到一个给定复制环境中唯一的GT ...

  10. 【python 】装饰器 (多个参数的函数,带参数的装饰器)【转】

    最简单的模板是这样的 #-*-coding:utf-8-*- def outer(func): def inner(): print 'before' func() print 'after' # r ...