14.1 一个异常示例
ZeroDivisionError异常:除0异常,因为在数学上除数不能为0。
ValueError异常:因为输入的是字符串。
14.2 异常类继承层次
Python中异常根类是BaseException。异常类继承层次如下:
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration
+-- StopAsyncIteration
+-- ArithmeticError
| +-- FloatingPointError
| +-- OverflowError
| +-- ZeroDivisionError
+-- AssertionError
+-- AttributeError
+-- BufferError
+-- EOFError
+-- ImportError
| +-- ModuleNotFoundError
+-- LookupError
| +-- IndexError
| +-- KeyError
+-- MemoryError
+-- NameError
| +-- UnboundLocalError
+-- OSError
| +-- BlockingIOError
| +-- ChildProcessError
| +-- ConnectionError
| | +-- BrokenPipeError
| | +-- ConnectionAbortedError
| | +-- ConnectionRefusedError
| | +-- ConnectionResetError
| +-- FileExistsError
| +-- FileNotFoundError
| +-- InterruptedError
| +-- IsADirectoryError
| +-- NotADirectoryError
| +-- PermissionError
| +-- ProcessLookupError
| +-- TimeoutError
+-- ReferenceError
+-- RuntimeError
| +-- NotImplementedError
| +-- RecursionError
+-- SyntaxError
| +-- IndentationError
| +-- TabError
+-- SystemError
+-- TypeError
+-- ValueError
| +-- UnicodeError
| +-- UnicodeDecodeError
| +-- UnicodeEncodeError
| +-- UnicodeTranslateError
+-- Warning
+-- DeprecationWarning
+-- PendingDeprecationWarning
+-- RuntimeWarning
+-- SyntaxWarning
+-- UserWarning
+-- FutureWarning
+-- ImportWarning
+-- UnicodeWarning
+-- BytesWarning
+-- ResourceWarning
从异常类的继承层次可见BaseException的子类很多,其中 Exception是非系统退出的异常,它包含了很多常用异常,如果自 定义异常需要继承Exception及其子类,不要直接继承 BaseException。另外,还有一类异常是Warning,Warning是警 告,提示程序潜在的问题。
提示:Python的异常类命名主要有Exception、Error和Warning。有时翻译为“错误”或“异常”,统一为异常。
14.3 常见异常
14.3.1 AttributeError异常
AttributeError异常试图访问一个类中不存在的成员(包括:成员变量、属性和成员方法)而引发的异常。
14.3.2 OSError异常
OSError是操作系统相关异常。Python3.3版本后IOError(输入输出异常)也并入OSError异常。
例如,未找到文件或磁盘已满。
FileNotFoundError属于OSError异常。
14.3.3 Indexerror异常
Indexerror异常是访问序列元素,下标索引超出取值范围所引发的异常。
14.3.4 KeyError异常
KeyError异常是试图访问字典里不存在的键时而引发的异常。
14.3.5 NameError异常
NameError是试图使用一个不存在的变量而引发的异常。
赋值时,如果变量不存在就会创建,不会发生异常。
14.3.6 TypeError异常
TypeError是试图传入变量类型与要求的不符合时而引发的异常。
14.3.7 ValueError异常
ValueError是传入一个无效的参数值而引发的异常。
14.4 捕获异常
当前函数没能力解决,则捕获异常进行处理;没有能力解决,则抛给上层调用者(函数)处理。如果上层调用者还无力解决,则继续抛给它的上层调用者。如果所在的函数都没有处理该异常,那么Python解释器会终止程序运行。
14.4.1 try-except语句
捕获异常是通过try-except语句实现的。最基本的语法:
try:
<可能会抛出异常的语句>
except [异常类型]:
<处理异常>
import datetime as dt
def read_date(in_date):
try:
date = dt.datetime.strptime(in_date, '%Y-%m-%d') # 可能会抛出ValueError异常
return date
except ValueError:
print('处理ValueError异常')
str_date = '2020-B-18' # '2020-B-18'
print('日期 = {0}'.format(read_date(str_date)))
datetime是Python内置的时间模块
还需要获得异常对象。
import datetime as dt
def read_date(in_date):
try:
date = dt.datetime.strptime(in_date, '%Y-%m-%d') # 可能会抛出ValueError异常
return date
except ValueError as e:
print('处理ValueError异常') #e是异常对象,print(e)指令可以打印异常对象,打印异常对象会输出描述异常对象的信息
print(e)
str_date = '2020-B-18' # '2020-B-18'
print('日期 = {0}'.format(read_date(str_date)))
14.4.2 多个except代码块
try:
<可能会抛出异常的语句>
except [异常类型1]:
<处理异常>
except [异常类型2]:
<处理异常>
....
except [异常类型n]:
<处理异常>
注意:当捕获的多个异常类之间存在父子关系,捕获异常顺序与except代码块的顺序有关。从上到下,先捕获子类,后捕获父类,否则子类捕获不到。
OSError是FileNotFoundError的父类,如果代码FileNotFoundError在后面,那么
FileNotFoundError永远不会进入。
import datetime as dt
def read_date_from_file(filename):
try:
file = open(filename)
in_date = file.read()
in_date = in_date.strip() # in_date.strip()提出字符串前后空白字符(包括空格、制表符、换行和回车等字符)
date = dt.datetime.strptime(in_date, '%Y-%m-%d') # 从文件中读字符串并解析为日期
return date
except ValueError as e:
print('处理ValueError异常')
print(e)
except FileNotFoundError as e:
print('处理FileNotFoundError异常')
print(e)
except OSError as e:
print('处理OSError异常')
print(e)
date = read_date_from_file('readme.txt')
print('日期 = {0}'.format(date))
14.4.3 try-except语句嵌套
import datetime as dt
def read_date_from_file(filename):
try:
file = open(filename)
try:
in_date = file.read()
in_date = in_date.strip() # in_date.strip()提出字符串前后空白字符(包括空格、制表符、换行和回车等字符)
date = dt.datetime.strptime(in_date, '%Y-%m-%d') # 从文件中读字符串并解析为日期
return date
except ValueError as e:
print('处理ValueError异常')
print(e)
except FileNotFoundError as e:
print('处理FileNotFoundError异常')
print(e)
except OSError as e:
print('处理OSError异常')
print(e)
date = read_date_from_file('readme.txt')
print('日期 = {0}'.format(date))
注意:try-except不仅可以嵌套在try代码块中,还可以嵌套在except代码块或finally代码块。嵌套会使程序流程变得复杂,尽量不用。
14.4.4 多重异常捕获
有些异常虽然类型不同,但捕获后的处理是相同的。
try:
<可能会抛出异常的语句>
except ValueError as e:
<调用方法method处理>
except OSError as e:
<调用方法method处理>
except FileNotFoundError as e:
<调用方法method处理>
三个不同类型的异常,捕获之后的处理都是method方法。可以把这些异常放到一个元组,这就是多重异常捕获。
import datetime as dt
def read_date_from_file(filename):
try:
file = open(filename)
in_date = file.read()
in_date = in_date.strip() # in_date.strip()提出字符串前后空白字符(包括空格、制表符、换行和回车等字符)
date = dt.datetime.strptime(in_date, '%Y-%m-%d') # 从文件中读字符串并解析为日期
return date
except (ValueError, OSError) as e:
print('处理ValueError异常')
print(e)
date = read_date_from_file('readme.txt')
print('日期 = {0}'.format(date))
14.5 异常堆栈跟踪
从程序员角度需要知道更加详细的异常信息,可以打印堆栈信息。可以通过Python内置模块traceback提供的print_exc()函数实现,print_exc()函数的语法格式:
traceback.print_exc(limit=None, file=None, chain=None)
limit限制堆栈跟踪个数,默认None不限制。
file判断是否输出堆栈跟踪信息到文件,默认None是不输出到文件。
chain为True,则将_cause_和_context_等属性串联起来,就像解释器本省打印未处理异常一样。
import datetime as dt
import traceback as tb
def read_date_from_file(filename):
try:
file = open(filename)
in_date = file.read()
in_date = in_date.strip() # in_date.strip()提出字符串前后空白字符(包括空格、制表符、换行和回车等字符)
date = dt.datetime.strptime(in_date, '%Y-%m-%d') # 从文件中读字符串并解析为日期
return date
except (ValueError, OSError) as e:
print('调用method处理...')
tb.print_exc()
date = read_date_from_file('readme.txt')
print('日期 = {0}'.format(date))
14.6 释放资源
有时try-except语句会占用一些资源,如打开文件、网络连接、打开数据库连接和使用数据结果集等,这些资源不能通过Python的垃圾收集回收,需要程序员释放。为了确保这些资源能够被释放,可以使用finally代码块或自动资源管理。
14.6.1 finally代码块
try-except语句后面可以跟一个finally代码块。try-except-finally语句语法如下:
try:
<可能会抛出异常的语句>
except [异常类型1]:
<处理异常>
except [异常类型2]:
<处理异常>
...
except [异常类型n]:
<处理异常>
finally :
<释放资源>
无论try正常结束还是except异常结束都会执行finally代码块。
import datetime as dt
def read_date_from_file(filename):
try:
file = open(filename)
in_date = file.read()
in_date = in_data.strip()
date = dt.datetime.strptime(in_date, '%Y-%m-%d')
return date
except ValueError as e:
print('处理ValueError异常')
except FileNotFoundError as e:
print('处理FileNotFoundError异常')
except OSError as e:
print('处理OSError异常')
finally:
file.close()
date = read_date_from_file('readme.txt')
print('日期 = {0}'.format(date))
14.6.2 else代码
else是在程序正常结束时执行的代码块。
import datetime as dt
def read_date_from_file(filename):
try:
file = open(filename)
except OSError as e:
print('打开文件失败')
else:
print('打开文件成功')
try:
in_date = file.read()
in_date = in_date.strip()
date = dt.datetime.strptime(in_date, '%Y-%m-%d')
return date
except ValueError as e:
print('处理ValueError异常')
except OSError as e:
print('处理OSError异常')
finally:
file.close()
date = read_date_from_file('readme.txt')
print('日期 = {0}'.format(date))
14.6.3 with as代码块自动资源管理
14.6.2示例的程序比较复杂,程序代码难以维护。
with as 代码块帮助自动释放资源,可以替代finally代码块,优化代码结构。
with as 提供了一个代码块,在as后面声明一个资源变量,当with as代码块结束之后自动释放资源。
import datetime as dt
def read_date_from_file(filename):
try:
with open(filename) as file:
in_date = file.read()
in_date = in_date.strip()
date = dt.datetime.strptime(in_date, '%Y-%m-%d')
return date
except ValueError as e:
print('处理ValueError异常')
except OSError as e:
print('处理OSError异常')
date = read_date_from_file('readme.txt')
print('日期 = {0}'.format(date))
with语句后面的open(fliename)语句可以创建资源对象,然后可以赋值给as后面的file变量。
注意:所有可以自动管理的资源,需要实现上下文管理协议(Content Management Protocol)
14.7 自定义异常类
有些公司为了提高代码的可重用性,自己开发了一些Python类库,其中有自己编写的一些异常类。实现自定义异常类需要继承Exception类或其子类。
class MyException(Exception):
def __init__(self, message): # message时异常描述信息
super().__init__(message)
# 调用父类构造方法,把参数message传入父类构造方法
14.8 显式抛出异常
本节之前的异常都是系统生成的,当异常抛出时,系统会创建一个异常对象,并将其抛出,也可以通过raise语句显式抛出异常,语法结构:
raise BaseException或其子类的实例
显式抛出的异常目的很多,例如不想某些异常传给上层调用者,可以捕获之后重新显式抛出另外一种异常给调用者。也就是异常自己处理。
import datetime as dt
class MyException(Exception):
def __init__(self, message): # message时异常描述信息
super().__init__(message)
# 调用父类构造方法,把参数message传入父类构造方法
def read_date_from_file(filename):
try:
file = open(filename)
in_date = file.read()
in_date = in_date.strip()
date = dt.datetime.strptime(in_date, '%Y-%m-%d')
return date
except ValueError as e:
raise MyException('不是有效日期')
except FileNotFoundError as e:
raise MyException('文件找不到')
except OSError as e:
raise MyException('文件无法打开或无法读取')
date = read_date_from_file('readme.txt')
print('日期 = {0}'.format(date))