在讨论动态捕获异常时让我大吃一惊的是,可以让我找到隐藏的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. 揭密FutureTask

    Future是我们在使用java实现异步时最常用到的一个类,我们可以向线程池提交一个Callable,并通过future对象获取执行结果.本篇文章主要讲述了JUC中FutureTask中的一些实现原理 ...

  2. Linux性能评估命令

    Linux性能评估工具 https://www.cnblogs.com/dianel/p/10085454.html Linux性能评估工具 目录 介绍 负载:uptime 查看内核的信息: dmes ...

  3. flask第二十三篇——模板【5】过滤器

    请关注微信公众号:自动化测试实战 过滤器——format格式化 flaskDemo.py # coding: utf-8 from flask import Flask, render_templat ...

  4. 初识安卓小程序(Android短信发送器)

    首先,先创建一个安卓项目(我的版本号是4.4.2的),名字为"短信发送器" 然后在res目录下找到layout目录,找到activity_main.xml或fragment_mai ...

  5. odoo 数据库选择的随笔

    odoo 数据库选择的随笔 看到有人讨论 odoo 是否可以使用 SQL Server(MS SQL). 原来 odoo 使用的是 PostgreSQL,PostgreSQL 是很先进的关系性数据库, ...

  6. TimeHelp 获取时间戳转换类

    using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Shar ...

  7. C#细说多线程(下)

    本文主要从线程的基础用法,CLR线程池当中工作者线程与I/O线程的开发,并行操作PLINQ等多个方面介绍多线程的开发. 其中委托的BeginInvoke方法以及回调函数最为常用.而 I/O线程可能容易 ...

  8. 【python】 使用 setuptools

    不会安装python的egg文件,在网上搜索了一下,被“蟒蛇蛋”这个词雷到了,记录下. 随着对python的逐渐使用,发现一些python组件是用一个包管理器发布的,今天搞了快一个小时,终于搞定了,这 ...

  9. java web Servlet开发(二)

    一.ServletConfig讲解 1.1.配置Servlet初始化参数 在Servlet的配置文件web.xml中,可以使用一个或多个<init-param>标签为servlet配置一些 ...

  10. PHP常用获取文件路径的函数集合整理

    转自: http://blog.sina.com.cn/s/blog_71ed1b870102vslg.html 我们在开发PHP项目过程中,经常会用到包含文件,所以有时候需要获取文件的相对路径或者绝 ...