所谓异常指的是程序的执行出现了非预期行为,就好比现实中的做一件事过程中总会出现一些意外的事。异常的处理是跨越编程语言的,和具体的编程细节相比,程序执行异常的处理更像是哲学。限于认知能力和经验所限,不可能达到像解释器下import this看到的python设计之禅一样,本文就结合实际使用简单的聊一聊。

0. 前言

工作中,程序员之间一言不合就亮代码,毕竟不管是代码本身还是其执行过程,不会存在二义性,更不会含糊不清,代码可谓是程序员之间的官方语言。但是其处理问题的逻辑或者算法则并非如此。

让我至今记忆犹新的两次程序员论剑有:

反问一:项目后期所有的异常处理都要去掉,不允许上线后出现未知的异常,把你这里的异常处理去掉,换成if else;

反问二:这里为什么要进行异常处理?代码都是你写的,怎么会出现异常呢?

这是我亲身经历的,不知道大家碰到这两个问题会怎样回答,至少我当时竟无言以对。这两个问题分别在不同的时间针对不同的问题出自一个互联网巨头中某个资深QA和资深开发的反问。

暂且不论对错,毕竟不同人考虑问题的出发点是不同的。但是从这么坚决的去异常处理的回答中至少有一点可以肯定,那就是很多人对自己的代码太过自信或者说是察觉代码潜在问题的直觉力不够,更别提正确的处理潜在的问题以保证重要业务逻辑的处理流程。写代码的时候如果只简单考虑正常的情况,那是在往代码中下毒。

接下类本篇博文将按照套路出牌(避免被Ctrl + W),介绍一下python的异常处理的概念和具体操作.

1. 为什么要异常处理

常见的程序bug无非就两大类:

  • 语法错误;
  • 逻辑不严谨或者思维混乱导致的逻辑错误;

显然第二种错误更难被发现,且后果往往更严重。无论哪一种bug,有两种后果等着我们:一、程序崩掉;二、执行结果不符合预期;

对于一些重要关键的执行操作,异常处理可以控制程序在可控的范围执行,当然前提是正确的处理。

比如我们给第三方提供的API或者使用第三方提供的API。多数情况下要正确的处理调用者错误的调用参数和返回异常结果的情况,不然就可能要背黑锅了。

在不可控的环境中运行程序,异常处理是必须的。然而困难的地方是当异常发生时,如何进行处理。

2. python异常处理

下面逐步介绍一下python异常处理相关的概念。

2.1 异常处理结构

必要的结构为try ... except,至少有一个except,else 和 finally 可选。

  1. try:
  2. code blocks
  3. except (Exception Class1, Exception Class2, ...) as e:
  4. catch and process exception
  5. except Exception ClassN:
  6. catch and process exception
  7. ... ...
  8. else:
  9. when nothing unexpected happened
  10. finally:
  11. always executed when all to end

2.2 python 内置异常类型

模块exceptions中包含了所有内置异常类型,类型的继承关系如下:

  1. BaseException
  2. +-- SystemExit
  3. +-- KeyboardInterrupt
  4. +-- GeneratorExit
  5. +-- Exception
  6. +-- StopIteration
  7. +-- StandardError
  8. | +-- BufferError
  9. | +-- ArithmeticError
  10. | | +-- FloatingPointError
  11. | | +-- OverflowError
  12. | | +-- ZeroDivisionError
  13. | +-- AssertionError
  14. | +-- AttributeError
  15. | +-- EnvironmentError
  16. | | +-- IOError
  17. | | +-- OSError
  18. | | +-- WindowsError (Windows)
  19. | | +-- VMSError (VMS)
  20. | +-- EOFError
  21. | +-- ImportError
  22. | +-- LookupError
  23. | | +-- IndexError
  24. | | +-- KeyError
  25. | +-- MemoryError
  26. | +-- NameError
  27. | | +-- UnboundLocalError
  28. | +-- ReferenceError
  29. | +-- RuntimeError
  30. | | +-- NotImplementedError
  31. | +-- SyntaxError
  32. | | +-- IndentationError
  33. | | +-- TabError
  34. | +-- SystemError
  35. | +-- TypeError
  36. | +-- ValueError
  37. | +-- UnicodeError
  38. | +-- UnicodeDecodeError
  39. | +-- UnicodeEncodeError
  40. | +-- UnicodeTranslateError
  41. +-- Warning
  42. +-- DeprecationWarning
  43. +-- PendingDeprecationWarning
  44. +-- RuntimeWarning
  45. +-- SyntaxWarning
  46. +-- UserWarning
  47. +-- FutureWarning
  48. +-- ImportWarning
  49. +-- UnicodeWarning
  50. +-- BytesWarning

2.3 except clause

excpet子句的常用的写法如下:

  • except:                         # 默认捕获所有类型的异常
  • except Exception Class:                   # 捕获Exception Class类型的异常
  • except Exception Class as e:                 # 捕获Exception Class类型的异常,异常对象赋值到e
  • except (Exception Class1, Exception Class2, ...) as e:      # 捕获列表中任意一种异常类型

上面的异常类可以是下面python内置异常类型,也可以是自定义的异常类型。

2.4 异常匹配原则

  • 所有except子句按顺序一一匹配,匹配成功则忽略后续的except子句;
  • 若抛出异常对象为except子句中给出的异常类型的对象或给出的异常类型的派生类对象,则匹配成功;
  • 如果所有的except子句均匹配失败,异常会向上传递;
  • 如果依然没有被任何try...except捕获到,程序在终止前会调用sys.excepthook进行处理;

2.5 else & finally

如果没有异常发生,且存在else子句,则执行else子句。只要存在finally子句,无论任何情况下都会被执行。

可能唯一不好理解的地方就是finally。没有异常、捕获异常、异常上传以及异常处理过程中发生异常等均会执行finally语句。

下面看个例子:

  1. def division(a, b):
  2. try:
  3. print'res = %s' % (a / b)
  4. except (ZeroDivisionError, ArithmeticError) as e:
  5. return str(e)  # 注意此处使用的是return
  6. else:
  7. print '%s / %s = %s' % (a, b, a / b)
  8. finally:
  9. print 'finally clause'

分别输入参数(1, 2),(1, 0)和 (1,“0”)执行:

print 'return value: %s' % division(a, b)

得到的结果如下:

  1. res = 0
  2. 1 / 2 = 0
  3. finally clause
  4. return value: None
  5.  
  6. finally clause
  7. return value: integer division or modulo by zero
  8.  
  9. finally clause
  10. Traceback (most recent call last):
  11. File "D:\My Folders\Cnblogs\Alpha Panda\Main.py", line 217, in <module>
  12. print 'return value: %s' % division(1, "")
  13. File "D:\My Folders\Cnblogs\Alpha Panda\Main.py", line 208, in division
  14. print'res = %s' % (a / b)
  15. TypeError: unsupported operand type(s) for /: 'int' and 'str'

可以看到纵使程序发生异常且没有被正确处理,在程序终止前,finally语句依旧被执行了。可以将此看做程序安全的最后一道有效屏障。主要进行一些善后清理工作,比如资源释放、断开网络连接等。当然with声明可以自动帮我们进行一些清理工作。

2.6 raise抛出异常

程序执行过程中可以使用raise主动的抛出异常.

  1. try:
  2. e = Exception('Hello', 'World')
  3. e.message = 'Ni Hao!'
  4. raise e
  5. except Exception as inst:
  6. print type(inst), inst, inst.args, inst.message

结果:<type 'exceptions.Exception'> ('Hello', 'World') ('Hello', 'World') Ni Hao!

上面展示了except对象的属性args, message。

2.7 自定义异常

绝大部分情况下内置类型的异常已经能够满足平时的开发使用,如果想要自定义异常类型,可以直接继承内置类型来实现。

  1. class ZeroDivZeroError(ZeroDivisionError):
  2. def __init__(self, value):
  3. self.value = value
  4. def __str__(self):
  5. return repr(self)
  6. def __repr__(self):
  7. return self.value
  8.  
  9. try:
  10. # do something and find 0 / 0
  11. raise ZeroDivZeroError('hahajun')
  12. except ZeroDivZeroError as err:
  13. print 'except info %s' % err

自定义异常应该直接继承自Exception类或其子类,而不要继承自BaseException.

3. Stack Trace

python执行过程中发生异常,会告诉我们到底哪里出现问题和什么问题。这两种类型的错误信息分别为stack trace和 exception,在程序中分别用traceback object和异常对象表示。

  1. Traceback (most recent call last):
  2. File "D:\My Folders\Cnblogs\Alpha Panda\Main.py", line , in <module>
  3. /
  4. ZeroDivisionError: integer division or modulo by zero

上面的错误信息包含错误发生时当前的堆栈信息(stack trace, 前三行)和异常信息(exception,最后一行),分别存放在traceback objects和抛出的异常对象中。

异常对象及异常信息前面已经介绍过,接下来我们在看一下异常发生时,stack trace的处理。

Traceback objects represent a stack trace of an exception. A traceback object is created when an exception occurs.

这时有两种情况:

  1. 异常被try...except捕获
  2. 没有被捕获或者干脆没有处理

正常的代码执行过程,可以使用traceback.print_stack()输出当前调用过程的堆栈信息。

3.1 捕获异常

对于第一种情况可以使用下面两种方式获取stack trace信息:

  1. trace_str = traceback.format_exc()
  1. 或者从sys.exc_info()中获取捕获的异常对象等的信息,然后格式化成trace信息。
  1. def get_trace_str(self):
  2. """
  3. 从当前栈帧或者之前的栈帧中获取被except捕获的异常信息;
  4. 没有被try except捕获的异常会直接传递给sys.excepthook
  5. """
  6. t, v, tb = sys.exc_info()
  7. trace_info_list = traceback.format_exception(t, v, tb)
  8. trace_str = ' '.join(trace_info_list)

至于抛出的包含异常信息的异常对象则可以在try...except结构中的except Exception class as e中获取。

3.2 未捕获异常

第二种情况,如果异常没有被处理或者未被捕获则会在程序推出前调用sys.excepthook将traceback和异常信息输出到sys.stderr。

  1. def except_hook_func(tp, val, tb):
  2. trace_info_list = traceback.format_exception(tp, val, tb)
  3. trace_str = ' '.join(trace_info_list)
  4. print 'sys.excepthook'
  5. print trace_str
  6. sys.excepthook = except_hook_func

上面自定义except hook函数来取代sys.excepthook函数。在hook函数中根据异常类型tp、异常值和traceback对象tb获取stack trace。这种情况下不能从sys.exc_info中获取异常信息。

3.3 测试

  1. def except_hook_func(tp, val, tb):
  2. trace_info_list = traceback.format_exception(tp, val, tb)
  3. trace_str = ' '.join(trace_info_list)
  4. print 'sys.excepthook'
  5. print trace_str
  6. sys.excepthook = except_hook_func
  7. try:
  8. 1 / 0
  9. except TypeError as e:
  10. res = traceback.format_exc()
  11. print "try...except"
  12. print str(e.message)
  13. print res

走的是sys.excepthook处理流程结果:

  1. sys.excepthook
  2. Traceback (most recent call last):
  3. File "D:\My Folders\Cnblogs\Alpha Panda\Main.py", line 259, in <module>
  4. 1 / 0
  5. ZeroDivisionError: integer division or modulo by zero

将except TypeError as e 改为 except ZeroDivisionError as e,则走的是try...except捕获异常流程,结果如下:

  1. try...except
  2. integer division or modulo by zero
  3. Traceback (most recent call last):
  4. File "D:\My Folders\Cnblogs\Alpha Panda\Main.py", line 259, in <module>
  5. 1 / 0
  6. ZeroDivisionError: integer division or modulo by zero

4. 异常信息收集

讲了这么多,我们看一下如何实现一个程序中trace信息的收集。

  1. class TracebackMgr(object):
  2.  
  3. def _get_format_trace_str(self, t, v, tb):
  4. _trace = traceback.format_exception(t, v, tb)
  5. return ' '.join(_trace)
  6.  
  7. def handle_one_exception(self):
  8. """
  9. 从当前栈帧或者之前的栈帧中获取被except捕获的异常信息;
  10. 没有被try except捕获的异常会自动使用handle_traceback进行收集
  11. """
  12. t, v, tb = sys.exc_info()
  13. self.handle_traceback(t, v, tb, False)
  14.  
  15. def handle_traceback(self, t, v, tb, is_hook = True):
  16. """
  17. 将此函数替换sys.excepthook以能够自动收集没有被try...except捕获的异常,
  18. 使用try except处理的异常需要手动调用上面的函数handle_one_exception才能够收集
  19. """
  20. trace_str = self._get_format_trace_str(t, v, tb)
  21. self.record_trace(trace_str, is_hook)
  22. # do something else
  23.  
  24. def record_trace(self, trace_str, is_hook):
  25. # Do somethind
  26. print 'is_hook: %s' % is_hook
  27. print trace_str

其用法很简单:

  1. trace_mgr = TracebackMgr()
  2. sys.excepthook = trace_mgr.handle_traceback
  3. try:
  4. 1 / 0
  5. except Exception as e:
  6. trace_mgr.handle_one_exception()
  7. # process trace
  8.  
  9. 1 / ''

结果用两种方式收集到两个trace信息:

  1. is_hook: False
  2. Traceback (most recent call last):
  3. File "D:\My Folders\Cnblogs\Alpha Panda\Main.py", line 299, in <module>
  4. 1 / 0
  5. ZeroDivisionError: integer division or modulo by zero
  6.  
  7. is_hook: True
  8. Traceback (most recent call last):
  9. File "D:\My Folders\Cnblogs\Alpha Panda\Main.py", line 304, in <module>
  10. 1 / ''
  11. TypeError: unsupported operand type(s) for /: 'int' and 'str'

可以将标准的输入和输出重定向,将打印日志和错误信息输入到文件中:

  1. class Dumpfile(object):
  2. @staticmethod
  3. def write(str_info):
  4. with open('./dump_file.txt', 'a+') as fobj:
  5. fobj.write(str_info)
  6.  
  7. def flush(self):
  8. self.write('')
  9. sys.stdout = sys.stderr = Dumpfile()

trace的收集主要用到两点:如何捕获异常和两种情况下异常信息的收集,前面都介绍过。

5. 总结

python 异常处理:

  1. 使用对象来表示异常错误信息,每种异常均有一种对应的类,BaseException为所有表示异常处理类的基类。
  2. 程序执行过程中抛出的异常会匹配该对象对应的异常类和其所有的基类。
  3. 可以从内置类型的异常类派生出自定义的异常类。
  4. 被捕获的异常可以再次被抛出。
  5. 可以的话尽量使用内置的替代方案,如if getattr(obj, attr_name, None),或者with结构等。
  6. sys.exc_info()保存当前栈帧或者之前的栈帧中获取被try, except捕获的异常信息。
  7. 未处理的异常导致程序终止前会被sys.excpethook处理,可以自定义定义sys.excpethook。

异常的陷阱:

正确的异常处理能让代码有更好的鲁棒性,但是错误的使用异常会过犹不及。

捕获异常却忽略掉或者错误的处理是不可取的。滥用异常处理不仅达不到提高系统稳定性的效果,还会隐藏掉引起错误的诱因,导致排查问题的难度增加。

因此比如何捕获异常更重要的是,异常发生时应当如何处理。

python异常处理的哲学的更多相关文章

  1. Python的设计哲学探究

    在Python shell中输入import this就会在屏幕上打印出来Python的设计哲学,如下: In [25]: import this The Zen of Python, by Tim ...

  2. python异常处理(基础)

    之前在学习python的时候有整理过python异常处理的文章,不够简单也不够完整,所以决定再整理一篇,算做补充. http://www.cnblogs.com/fnng/archive/2013/0 ...

  3. Python异常处理 分类: python Raspberry Pi 服务器搭建 2015-04-01 13:22 172人阅读 评论(0) 收藏

    一个程序要保持稳定运行必须要有异常处理,本文将简单介绍Python中的try-except..异常处理语句的使用. 该种异常处理语法的规则是: 执行try下的语句,如果引发异常,则执行过程会跳到第一个 ...

  4. Python 异常处理--raise函数用法

    raise语句手工引发一个异常: "raise" [expression ["," expression ["," expression]] ...

  5. [Python学习笔记][第八章Python异常处理结构与程序调试]

    1/30 第八章Python异常处理结构与程序调试 异常处理 try-except结构 try: try块 except Exception: except块 try-except-else结构 tr ...

  6. python异常处理try,except,else,finally,raise

    先看下else的使用: try: ... exception: ... else: ... 只有在try中没有发生任何异常,所有代码完全成功的情况下才会转入else 再看下finally: final ...

  7. Python 异常处理

    Python 异常处理 python提供了两个非常重要的功能来处理python程序在运行中出现的异常和错误.你可以使用该功能来调试python程序. 异常处理: 本站Python教程会具体介绍. 断言 ...

  8. Python异常处理总结

    一.何谓异常处理 在我们调试程序时,经常不可避免地出现意料之外的情况,导致程序不得不停止运行,然后提示大堆提示信息,大多是这种情况都是由异常引起的.异常的出现一方面是因为写代码时粗心导致的语法错误,这 ...

  9. python异常处理与断言以及日志模块

    python异常处理与断言 目录: 1.异常处理 2.断言(assert) 3.日志模块(logging) 4.修改之前的车票信息查询,把日志模块.异常处理加进去 1.异常处理 代码如下: 语法: t ...

随机推荐

  1. BZOJ_3191_[JLOI2013]卡牌游戏_概率DP

    BZOJ_3191_[JLOI2013]卡牌游戏_概率DP Description   N个人坐成一圈玩游戏.一开始我们把所有玩家按顺时针从1到N编号.首先第一回合是玩家1作为庄家.每个回合庄家都会随 ...

  2. mysql输入中文出现ERROR 1366

    MySQL输入中文出现如下错误: ERROR 1366: 1366: Incorrect string value: '\xE6\xB0\xB4\xE7\x94\xB5...' for column ...

  3. springboot redis多数据源

    springboot中默认的redis配置是只能对单个redis库进行操作的. 那么我们需要多个库操作的时候这个时候就可以采用redis多数据源. 本代码参考RedisAutoConfiguratio ...

  4. 第十四章——循环神经网络(Recurrent Neural Networks)(第二部分)

    本章共两部分,这是第二部分: 第十四章--循环神经网络(Recurrent Neural Networks)(第一部分) 第十四章--循环神经网络(Recurrent Neural Networks) ...

  5. 微信jssdk config:invalid signature 签名错误 ,问题排查过程

    invalid signature签名错误.建议按如下顺序检查: 确认签名算法正确,可用 http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisi ...

  6. [Swift]LeetCode1033. 移动石子直到连续 | Moving Stones Until Consecutive

    Three stones are on a number line at positions a, b, and c. Each turn, let's say the stones are curr ...

  7. MySQL - 扩展性 2 扩展策略:氪金氪脑任君选

    如果将应用的所有数据简单地放在一台 MySQL 服务器实例上,就不用谈什么扩展性了.但是业务能稳定持续的增长,那么应用肯定会碰到性能瓶颈. 对于很多类型的应用而言,购买更高性能的机器能解决一大部分性能 ...

  8. asp.net core系列 28 EF模型配置(字段,构造函数,拥有实体类型)

    一. 支持字段 EF允许读取或写入字段而不是一个属性.在使用实体类时,用面向对象的封装来限制或增强应用程序代码对数据访问的语义时,这可能很有用.无法使用数据注释配置.除了约定,还可以使用Fluent ...

  9. MongoDB之基本操作与日常维护

    MongoDB基本操作 MongoDB的基本操作主要是对数据库.集合.文档的操作,包括创建数据库.删除数据库.插入文档.更改文档.删除文档.和查询文档. 操作 描述 show dbs 查看当前实例下的 ...

  10. 结合JDK源码看设计模式——适配器模式

    定义: 将一个类的接口转换成客户期望的另外一个接口(重点理解适配的这两个字),使得接口不兼容的类可以一起工作适用场景: 已经存在的类,它的方法和需求不匹配的时候 在软件维护阶段考虑的设计模式 详解 首 ...