异常处理在任何一门编程语言里都是值得关注的一个话题。就像写函数时肯定是判断异常处理,然后在写业务逻辑代码,这样代码才更健壮。
每当发生让Python不知所措的错误时,它都会创建一个异常对象。如果你编写了处理异常的代码,程序将继续运行;如果你未对异常进行处理,程序将停止,并且显示一个traceback,其中包含有关异常的报告。本文对Python异常处理进行讲解。
Python内置异常
Python的异常处理能力是很强大的,它有很多内置异常,可向用户准确反馈出错信息。在Python中,异常也是对象,可对它进行操作。BaseException是所有内置异常的基类,但用户定义的类并不直接继承BaseException,所有的异常类都是从Exception继承,且都在exceptions模块中定义。Python自动将所有异常名称放在内建命名空间中,所以程序不必导入exceptions模块即可使用异常。一旦引发而且没有捕捉SystemExit异常,程序执行就会终止。如果交互式会话遇到一个未被捕捉的SystemExit异常,会话就会终止。
内置异常类的层次结构如下:
BaseException # 所有异常的基类 ±- SystemExit # 解释器请求退出 ±- KeyboardInterrupt # 用户中断执行(通常是输入^C) ±- GeneratorExit # 生成器(generator)发生异常来通知退出 ±- Exception # 常规异常的基类 ±- StopIteration # 迭代器没有更多的值 ±- StopAsyncIteration # 必须通过异步迭代器对象的__anext__()方法引发以停止迭代 ±- ArithmeticError # 各种算术错误引发的内置异常的基类 | ±- FloatingPointError # 浮点计算错误 | ±- OverflowError # 数值运算结果太大无法表示 | ±- ZeroDivisionError # 除(或取模)零 (所有数据类型) ±- AssertionError # 当assert语句失败时引发 ±- AttributeError # 属性引用或赋值失败 ±- BufferError # 无法执行与缓冲区相关的操作时引发 ±- EOFError # 当input()函数在没有读取任何数据的情况下达到文件结束条件(EOF)时引发 ±- ImportError # 导入模块/对象失败 | ±- ModuleNotFoundError # 无法找到模块或在在sys.modules中找到None ±- LookupError # 映射或序列上使用的键或索引无效时引发的异常的基类 | ±- IndexError # 序列中没有此索引(index) | ±- KeyError # 映射中没有这个键 ±- MemoryError # 内存溢出错误(对于Python 解释器不是致命的) ±- NameError # 未声明/初始化对象 (没有属性) | ±- UnboundLocalError # 访问未初始化的本地变量 ±- OSError # 操作系统错误,EnvironmentError,IOError,WindowsError,socket.error,select.error和mmap.error已合并到OSError中,构造函数可能返回子类 | ±- BlockingIOError # 操作将阻塞对象(e.g. socket)设置为非阻塞操作 | ±- ChildProcessError # 在子进程上的操作失败 | ±- ConnectionError # 与连接相关的异常的基类 | | ±- BrokenPipeError # 另一端关闭时尝试写入管道或试图在已关闭写入的套接字上写入 | | ±- ConnectionAbortedError # 连接尝试被对等方中止 | | ±- ConnectionRefusedError # 连接尝试被对等方拒绝 | | ±- ConnectionResetError # 连接由对等方重置 | ±- FileExistsError # 创建已存在的文件或目录 | ±- FileNotFoundError # 请求不存在的文件或目录 | ±- InterruptedError # 系统调用被输入信号中断 | ±- IsADirectoryError # 在目录上请求文件操作(例如 os.remove()) | ±- NotADirectoryError # 在不是目录的事物上请求目录操作(例如 os.listdir()) | ±- PermissionError # 尝试在没有足够访问权限的情况下运行操作 | ±- ProcessLookupError # 给定进程不存在 | ±- TimeoutError # 系统函数在系统级别超时 ±- ReferenceError # weakref.proxy()函数创建的弱引用试图访问已经垃圾回收了的对象 ±- RuntimeError # 在检测到不属于任何其他类别的错误时触发 | ±- NotImplementedError # 在用户定义的基类中,抽象方法要求派生类重写该方法或者正在开发的类指示仍然需要添加实际实现 | ±- RecursionError # 解释器检测到超出最大递归深度 ±- SyntaxError # Python 语法错误 | ±- IndentationError # 缩进错误 | ±- TabError # Tab和空格混用 ±- SystemError # 解释器发现内部错误 ±- TypeError # 操作或函数应用于不适当类型的对象 ±- ValueError # 操作或函数接收到具有正确类型但值不合适的参数 | ±- UnicodeError # 发生与Unicode相关的编码或解码错误 | ±- UnicodeDecodeError # Unicode解码错误 | ±- UnicodeEncodeError # Unicode编码错误 | ±- UnicodeTranslateError # Unicode转码错误 ±- Warning # 警告的基类 ±- DeprecationWarning # 有关已弃用功能的警告的基类 ±- PendingDeprecationWarning # 有关不推荐使用功能的警告的基类 ±- RuntimeWarning # 有关可疑的运行时行为的警告的基类 ±- SyntaxWarning # 关于可疑语法警告的基类 ±- UserWarning # 用户代码生成警告的基类 ±- FutureWarning # 有关已弃用功能的警告的基类 ±- ImportWarning # 关于模块导入时可能出错的警告的基类 ±- UnicodeWarning # 与Unicode相关的警告的基类 ±- BytesWarning # 与bytes和bytearray相关的警告的基类 ±- ResourceWarning # 与资源使用相关的警告的基类。被默认警告过滤器忽略。
异常的发生
下面来看一种导致Python引发异常的简单错误。如果在数字中除以0,会发生异常。例子:
try_except_test.py
代码语言:javascript复制print(8/0)
编译运行:
显然,Python无法这样做,因此你会将看到。编译时指出的错误ZeroDivisionError是一个异常对象。
Python无法按照你的要求去做,就会创建这种对象。在这种情况下,Python将停止运行程序,并发出引发了哪种异常,而我们可根据这些信息对程序进行修改。
下面我们将告诉Python发生错误时怎么办?
异常捕获
python的异常捕获常用try…except…结构,把可能发生错误的语句放在try模块里,用except来处理异常,每一个try,都必须至少对应一个except。此外,与python异常相关的关键字主要有:
处理ZeroDivisionError异常的try-except代码块类似于下面的这样:
代码语言:javascript复制a=8
b=0
try:
c=a/b
print(c)
except ZeroDivisionError as e:
print(e)
print("done")
编译运行:
可以看出在有异常的情况下,程序依然执行完毕。
上面的例子用print©放在了一个try代码块中。如果try代码块中的代码运行起来没有问题,Python将跳过except代码块;
如果try代码块中的代码导致了错误,Python将查找查找这样的execpt代码块,并运行其中的代码,即其中的指定的错误与引发的错误相同。
try …except…else
语法:
代码语言:javascript复制try:
正常的操作
......................
except(Exception1[, Exception2[,...ExceptionN]]]):
发生以上多个异常中的一个,执行这块代码
......................
else:
如果没有异常执行这块代码
还是上面的例子,把c=a/b改为c=b/a,然后在加上else判断条件:
代码语言:javascript复制a=8
b=0
try:
c = b/ a
print (c)
except (IOError ,ZeroDivisionError) as x:
print (x)
else:
print ("no error")
print ("done")
编译运行:
try-execpt-else代码块的工作原理大致如下:
Python尝试执行try代码块中的代码块;只有可能引发异常的代码才需要放在try语句中。
有时候,有一些仅在try代码块成功执行时才需要运行的代码。这些代码应放在else代码块中。
通过预测可能发生错误的代码,可编写健壮的程序,它们即便面临无效数据或缺少资源,也能继续运行,从而能够抵御无意的用户错误和恶意的攻击。
raise 引发一个异常
raise是引发一个异常来检查某个条件是否成立。raise语法格式如下:
代码语言:javascript复制raise [Exception [, args [, traceback]]]
语句中Exception是异常的类型(例如ValueError),参数是一个异常参数值。该参数是可选的,如果不提供,异常的参数是"None"。最后一个参数是跟踪异常对象,也是可选的(在实践中很少使用)。
代码语言:javascript复制def not_zero(num):
try:
if num == 0:
raise ValueError('参数错误')
return num
except Exception as e:
print(e)
not_zero(0)
编译运行:
try …finally
try中包含了finally子句,python一定会在try语句后执行其语句代码块,无论try代码块执行时是否发生异常。
代码语言:javascript复制a=8
b=0
try:
print (a/b)
finally:
print ("always excute")
编译运行:
可以看到,无论异常是否发生,在程序结束前,finally中的语句都会被执行。异常也照常报错。
try …except…finally
代码语言:javascript复制a=8
b=0
try:
print (a/b)
except:
print("error")
finally:
print ("always excute")
编译运行:
用户自定义异常
你也可以通过创建一个新的异常类拥有自己的异常,异常应该是通过直接或间接的方式继承自Exception类。下面创建了一个MyError类,基类为Exception,用于在异常触发时输出更多的信息。
代码语言:javascript复制class MyException(Exception):
def __init__(self,message):
Exception.__init__(self)
self.message=message
a=9
if a<10:
try:
raise MyException("my excepition is raised ")
except MyException as e:
print (e.message)
编译运行:
把异常保存到一个日志文件中,来分析这些异常
代码语言:javascript复制import traceback
try:
print ('here1:',5/2)
print ('here2:',10/5)
print ('here3:',10/0)
except Exception as e:
f=open("log.txt",'a')
#traceback.print_exc(file=f) # 打印输出到屏幕
traceback.print_exc(file=f) # 输出到文件
f.flush()
f.close()
编译运行:
在上面的例子中,把异常保存到一个日志文件(log.txt)。
采用traceback模块查看异常。发生异常时,Python能“记住”引发的异常以及程序的当前状态。Python还维护着traceback(跟踪)对象,其中含有异常发生时与函数调用堆栈有关的信息。
总结
这里要提请一下:try语句必须有一个except或一个finally,else是可选的,但是如果有else ,则必须至少有一个except。
被检测的代码块抛出的异常有多种可能性,并且我们针对所有的异常类型都只用一种处理逻辑就可以了,这个时候可以使用Exception,因为它是常规异常的基类。除非要对每一特殊异常进行特殊处理。