# 异常
# 异常类型
C 语言程序运行时,可能在终端打印一些报错信息。而 Python 程序运行时,将各种报错信息,表示为异常(exception)的形式。
Python 内置了几十种异常。每种异常,由一个 class 定义。
- 常见的一些异常:
- BaseException # 这是所有异常类的祖先类 - SystemExit # 关于系统的严重异常 - KeyboardInterrupt # 当用户通过键盘输入 Ctrl + C 打断 Python 程序运行时,会抛出这种异常 - Exception # 普通的异常 - AssertionError - AttributeError # 访问对象的一个属性时,如果该属性不存在,则抛出这种异常 - ImportError # 用关键字 import 导入一个 Python 模块时,如果导入失败,则抛出这种异常 - KeyError # 访问 dict 对象中的一个 key 时,如果该 key 不存在,则抛出这种异常 - NameError # 如果 Python 代码中访问了一个标识符,但当前作用域中没有找到该标识符,则抛出这种异常 - OSError # 表示与操作系统相关的异常,例如 FileNotFoundError 是 OSError 的子类 - RuntimeError # 表示 Python 运行时的一般错误。如果用户想创建一个异常,但不属于 NameError 等已有的异常类型,则可以粗略地归类为 RuntimeError - SyntaxError # 语法错误 - SystemError # 表示 Python 解释器的内部错误 - TypeError # 如果某个对象的类型错误,则抛出这种异常。例如期望某个变量为 int 类型,实际发现为 str 类型 - ValueError # 如果某个对象的取值错误,则抛出这种异常 - Warning # 表示警告,不是严重的报错 - DeprecationWarning # 表示某个功能被弃用 - RuntimeWarning # 表示 Python 运行时的一般警告
- 例:
>>> NameError # 这是一种异常类型,或者说一种异常名称 <class 'NameError'> >>> NameError.mro() # 查看这种异常的祖先类 [<class 'NameError'>, <class 'Exception'>, <class 'BaseException'>, <class 'object'>] >>> NameError() # 这是一个 NameError 类型的异常,或者说 NameError 类的一个实例 NameError()
- 常见的一些异常:
用户可以自己定义一个异常类,如下:
>>> class MyException(Exception): # 建议继承 Exception 类 ... def __init__(self, msg=''): ... self.msg = msg ... def __repr__(self): ... return 'MyException({})'.format(repr(self.msg)) ... def __str__(self): ... return self.__repr__() ... >>> raise MyException('test') Traceback (most recent call last): File "<stdin>", line 1, in <module> __main__.MyException: MyException('test')
# 抛出异常
# raise
用关键字
raise
可以产生一个异常,又称为抛出异常。抛出异常的影响:
- 打开 Python 交互式终端时,如果抛出一个异常,则会在终端打印报错信息。例:
>>> a # 在终端输入一个未定义的变量,就会产生异常 Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'a' is not defined >>> raise NameError('test') # 这是创建 NameError 类型异常的一个实例,传入一个字符串作为描述,然后用 raise 抛出 Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: test
- 如果抛出的异常,属于 SystemExit 类型,则打印报错信息之后,还会导致 Python 终端退出。
- 运行 Python 脚本时,如果抛出异常到全局作用域,则会导致 Python 脚本退出。
- 为了让 Python 脚本保持运行,
- 用户可以捕捉异常,避免它被抛到全局作用域。
- 用户可以在 Python 脚本退出之后,重新启动 Python 脚本。
- 为了让 Python 脚本保持运行,
- 打开 Python 交互式终端时,如果抛出一个异常,则会在终端打印报错信息。例:
# assert
- 用关键字 assert 可以执行一个条件表达式,如果表达式结果为 False ,则抛出 AssertionError 异常。这种语法称为断言。
- 例:
>>> assert 1 == 0 Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError >>> assert 1 == 0, 'not equal' # 可以给该异常,添加字符串描述 Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError: not equal
# 捕捉异常
# except
用关键字
except
可以捕捉异常。- 语法格式如下:
try: # 有且仅有一个 try 语句块,用于尝试执行一段代码 ... except ...: # 可以输入任意个 except 语句块,用于处理某些异常 ... else: ... finally: ...
- 运行逻辑:
- 首先执行 try 语句块,如果产生了异常,则终止执行 try 语句块,然后,
- 如果当前异常类型,属于某个 except 指定的异常类或其子孙类,则执行该 except 语句块。
- 如果存在多个匹配的 except 子句,则只执行最先出现的那个 except 语句块。
- 如果不存在匹配的 except 子句,则抛出当前异常。(从 try 语句块,抛到 try 之外的语句块)
- 在 try-except 语句块之后,可选添加一个 else 语句块。如果 try 语句块没有产生异常,则会执行 else 语句块。
- 首先执行 try 语句块,如果产生了异常,则终止执行 try 语句块,然后,
- 语法格式如下:
例:
>>> try: ... 1 + 'A' ... except Exception as e: # 捕捉一个 Exception 类型的异常,用关键字 as 赋值给变量 e ... print(type(e)) # 通过变量 e ,可以知道当前异常的具体信息 ... print(str(e)) ... <class 'TypeError'> unsupported operand type(s) for +: 'int' and 'str'
- Exception 是大部分类型的异常的祖先类。因此,如果在 except 语句块中指定 Exception 类,则可以匹配大部分类型的异常。
- 实践中,建议不指定 Exception 类,而是指定一些具体的异常类型,从而准确地捕捉异常。
例:
>>> import sys >>> try: ... 1 + 'A' ... except TypeError: # 捕捉异常时,可以不用关键字 as 赋值给变量 e ... print(sys.exc_info()[0]) # 此时没有变量 e ,调用 sys.exc_info() 才能知道当前异常的具体信息,该函数会返回一个三元组 ... print(sys.exc_info()[1]) ... print(sys.exc_info()[2]) ... <class 'TypeError'> # 异常的类型 unsupported operand type(s) for +: 'int' and 'str' # 异常的字符串描述 <traceback object at 0x000001B326D554C0> # 异常实例
例:
>>> import sys >>> try: ... 1 + 'A' ... except (NameError, KeyError) as e: # 同一个 except 语句中,可以尝试捕捉多种异常 ... print(type(e)) ... print(str(e)) ... except: # 最后一个 except 语句,可以不指定异常类型,从而匹配所有类型的异常 ... print('Unexpected error:', sys.exc_info()[0], sys.exc_info()[1]) ... Unexpected error: <class 'TypeError'> unsupported operand type(s) for +: 'int' and 'str'
在 except 语句块中使用 raise :
- 直接执行 raise ,会报错说没有指定异常:
>>> raise Traceback (most recent call last): File "<stdin>", line 1, in <module> RuntimeError: No active exception to reraise
- 在 except 语句块中,可以直接执行 raise ,这会将当前捕捉的异常,重新抛出:
>>> try: ... 1 + 'A' ... except: ... raise ... Traceback (most recent call last): File "<stdin>", line 2, in <module> TypeError: unsupported operand type(s) for +: 'int' and 'str'
- 在 except 语句块中,已经捕捉了一个异常。如果用 raise 再抛出一个异常,则同时存在两个异常。这些异常被 Python 解释器暂存在 traceback 堆栈中,称为异常链。
>>> try: ... 1 + 'A' ... except: ... raise RuntimeError('an error') ... Traceback (most recent call last): File "<stdin>", line 2, in <module> TypeError: unsupported operand type(s) for +: 'int' and 'str' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "<stdin>", line 4, in <module> RuntimeError: an error
- 可用
raise ... from ...
的语法,表示当前异常,是由某个异常造成的。>>> try: ... 1 + 'A' ... except TypeError as e: ... raise RuntimeError('an error') from e ... Traceback (most recent call last): File "<stdin>", line 2, in <module> TypeError: unsupported operand type(s) for +: 'int' and 'str' The above exception was the direct cause of the following exception: # 这里说明了因果关系 Traceback (most recent call last): File "<stdin>", line 4, in <module> RuntimeError: an error
- 可用
raise ... from None
的语法,删除已经产生的异常,只留下当前异常。>>> try: ... 1 + 'A' ... except TypeError as e: ... raise RuntimeError('an error') from None ... Traceback (most recent call last): File "<stdin>", line 4, in <module> RuntimeError: an error
- 直接执行 raise ,会报错说没有指定异常:
# finally
- 在 try-except 语句块之后,可选添加一个 finally 语句块。不管 try-except 语句块是否产生异常,都会执行 finally 语句块。
- finally 语句块的优先级很高,即使在 try-except 语句块中执行了 break、continue、return 语句来退出,也会在退出之前,先执行 finally 语句块。
>>> def fun1(): ... try: ... print('try start') ... raise RuntimeError('try error') # 执行到这里时,跳转到 except 语句块 ... return 'try return' ... print('try end') ... except: ... print('except start') ... raise RuntimeError('except error') ... return 'except return' # 执行到这里时,跳转到 finally 语句块 ... print('except end') ... finally: ... return 'finally return' ... >>> fun1() try start except start 'finally return'
# with
- 使用关键字 finally ,可以不管是否产生异常,都保证在最后执行一段代码,比如清理现场、记录日志。
- 使用关键字 with ,也可以实现这种效果:创建某个类的一个实例,对该实例进行任意操作,最后保证对该实例执行清理操作。
- 前提条件:被关键字 with 操作的这个类,必须实现了魔法方法
__enter__()
和__exit__()
。 - 例:
class Test: def __init__(self, filename): self.filename = filename def __enter__(self): self.f = open(self.filename, self.mode) return self.f def __exit__(self, name, value, trace): # 产生异常时,会调用该方法,并传入异常名称、异常内容、traceback 对象 self.f.close() with Test('1.txt') as t: t.read()
- Python 解释器执行
with Test('1.txt') as t
时,会先创建对象t = Test('1.txt')
,然后自动调用t.__enter__()
。 - 当 with 语句块执行完毕,或者抛出异常时,都会自动调用
t.__exit__()
。
- Python 解释器执行
- 前提条件:被关键字 with 操作的这个类,必须实现了魔法方法