1、问题
无论开发大型程序还是自动化开发者自己的日常杂事,不可避免的会碰到bug、以前没有考虑的问题、系统故障等等问题。这时需要程序尽可能保持稳定,完成基本清理,提供清晰的问题栈线索和运行日志,便于分析查错。python的异常处理就提供这样一个机制。
2、处理流程
python的异常处理的基本语法如下:
try: <正常运行的程序逻辑> except <异常类型1>: <异常类型1处理> except <异常类型...>: <异常类型...处理> except <异常类型N>: <异常类型N处理> except: <未知类型类型处理> else: <除异常外,还需要运行的程序逻辑> finally: <无论正常还是异常都需要的最后处理和清理>
python系统提供了一些基本的异常类型,每一个python包一般也会提供相应的定制异常,开发者也可以自己定义异常类型。
在python程序中也可以产生异常:
>>> raise NameError('true') Traceback (most recent call last): File "<pyshell#17>", line 1, in <module> raise NameError('true') NameError: true
2.1、语法异常:SyntaxError和NameError
运行python代码的时候,语法异常是常遇到的问题。
有时候是使用的关键词不对,或是变量未定义,系统会报告NameError。比如下例中,true不是python的关键词True,也不是定义的变量:
>>> while true: print (0/0) Traceback (most recent call last): File "<pyshell#2>", line 1, in <module> while true: NameError: name 'true' is not defined
有时候是语法问题,系统会报告SyntaxError:
>>> if True SyntaxError: invalid syntax
这个例子中,缺少条件语句行需要的“:”号结尾符。
2.2、基本系统异常类型
python系统提供了一些基本的异常类型,下面是一些典型的内置系统异常:
- AssertError:
assert
语句出错 - EOFError:输入input()遇到文件末尾
- FloatingPointError:浮点数错误
- ImportError/ModuleNotFoundError:装载包出错
- IndexError:索引出界
- KeyError:字典不存在要查询的键,可以用in关键词来检验
- NameError:无法找到局部或全局变量
- NotImplementedError:调用没有定义方法,是RuntimeError的子类
- OSError:系统调用出错,比如I/O,或是无硬盘空间了
- OverflowError:数值计算溢出
- RuntimeError:运行出错
- SystemError:
- TypeError:类型错误
- UnicodeError:unicode编码和译码错误
- ValueError:数值类型不匹配
- ZeroDivisionError:除数是0
所有python系统内置的异常可以在下面文档中获得:
[python3]:https://docs.python.org/3/library/exceptions.html#bltin-exceptions
[python2]:https://docs.python.org/2/library/exceptions.html#bltin-exceptions
2.3、定制异常类型
python程序中可以定义自己异常,这样程序的使用者可以针对这种异常作特殊的处理。比如,网络连接出错可以重试。
class Error(Exception): """定义本模块的出错基类.""" pass class InputError(Error): """输入出错. 属性: expression -- 出错的输入表达式 message -- 出错的信息 """ def __init__(self, expression, message): self.expression = expression self.message = message class StateTransitionError(Error): """系统执行了错误的状态转移. 属性: previous -- 初始状态 next -- 未来状态 message -- 状态转移出错信息 """ def __init__(self, previous, next, message): self.previous = previous self.next = next self.message = message
2.4、获取系统出错信息
python系统或其他软件包提供的异常类型都会提供出错信息。但是有时候还不够,比如,我们需要得到系统的访问栈,需要获得一些系统运行状态的信息。
我们可以使用traceback包获得访问栈的信息:
import traceback def get_calltrace(): try: a = 1 b = 0 c = a/b except: tb = traceback.format_exc() else: tb = "No error" finally: print (tb) >>> get_calltrace() Traceback (most recent call last): File "<pyshell#28>", line 5, in get_calltrace ZeroDivisionError: division by zero
有时候,需要从系统获取更多信息,可以调用sys.exc_info() :
import sys, traceback
def get_calltrace(): try: a = 1 b = 0 c = a/b except: tb = traceback.format_exc() exc= sys.exc_info() else: tb = "No error" exc = None finally: print (tb) if exc: print (exc[0]) print (exc[1]) print (exc[2])
3、assert和参数检查
当我们设计程序的时候,常常需要考虑如何让系统更加稳定,帮助开发者尽早发现问题,让最终客户拥有最棒的使用体验。
- assert:主要为内部开发使用,用于验证假设,帮助程序设计尽早发现问题。
- 参数检查:主要为外部API开发使用,用于验证输入参数,帮助程序设计尽早发现问题;调用程序必须检查返回值是否出错。
3.1、assert
不加检验的函数总会出错,而且出错信息无法控制:
def add(a, b): return a + b >>> add('hello', 7) Traceback (most recent call last): File "<pyshell#45>", line 1, in <module> add('hello', 7) File "<pyshell#44>", line 2, in add return a + b TypeError: must be str, not int
我们可以用assert来验证调用输入的有效性,并提供清晰提示:
def add_assert(a, b): assert isinstance(a, (int, float)), "a isn't numeric" assert isinstance(b, (int, float)), "b isn't numeric" return a + b >>> add_assert('hello', 7) Traceback (most recent call last): File "<pyshell#69>", line 1, in <module> add_assert('hello', 7) File "<pyshell#68>", line 2, in add_assert assert isinstance(a, (int, float)), "a isn't numeric" AssertionError: a isn't numeric
3.2、参数检查
如果某个函数是开放的API,需要做输入检查,并返回出错代码提示调用者:
def add_validate(a, b): if not isinstance(a, (int, float)): return ("Fail", "a isn't numeric") if not isinstance(b, (int, float)): return ("Fail", "b isn't numeric") return ("OK", a+b) >>> add_validate('hello', 7) ('Fail', "a isn't numeric") >>> add_validate(5.0, 7) ('OK', 12.0)
4、还需注意的问题
异常总会发生,对未知异常在程序中必须捕作并处理,否则会导致系统崩溃。
在捕捉异常的时候,需要先捕捉特殊的异常,最后捕捉普通的,未知的异常。
记得在finally语句里释放分配的资源。虽然python有垃圾回收处理逻辑,不及时地释放资源是python程序出现内存泄露的主因。