异常是什么
Python 使用异常对象来表示异常状态,并在遇到错误时引发异常。异常对象未被处理(或捕获)时,程序将终止并显示一条错误消息(traceback)。
如果异常只能用来显示错误消息,就没多大意思了。但事实上,每个异常都是某个类的实例(这里是 ZeroDivisionError
)。你能以各种方式引发和捕获这些实例,从而逮住错误并采取措施,而不是放任错误导致整个程序失败。
常见的异常
除零错误
1/0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
数组越界错误
a = [1, 2, 3]
print(a[3])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
运算符错误
a = 123
b = '123'
print(a+b)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
正如你看到的,出现问题时,将自动引发异常。
捕捉异常
try/except 语句
异常捕捉可以使用 try/except
语句。
try:
1/0
except Exception as error:
print('发生如下错误:',error)
发生如下错误: division by zero
try
语句按照如下方式工作:
- 首先,执行
try
子句(在关键字try
和关键字except
之间的语句)。 - 如果没有异常发生,忽略
except
子句,try
子句执行后结束。 - 如果在执行
try
子句的过程中发生了异常,那么try
子句余下的部分将被忽略。如果异常的类型和except
之后的名称相符,那么对应的except
子句将被执行。 - 如果一个异常没有与任何的
except
匹配,那么这个异常将会传递给上层的try
中。
一个 try
语句可能包含多个 except
子句,分别来处理不同的特定的异常。最多只有一个分支会被执行。
处理程序将只针对对应的 try
子句中的异常进行处理,而不是其他的 try
的处理程序中的异常。
一个 except
子句可以同时处理多个异常,这些异常将被放在一个括号里成为一个元组,例如:
except (RuntimeError, TypeError, NameError):
pass
try/except...else 语句
try/except
语句还有一个可选的 else
子句,如果使用这个子句,那么必须放在所有的 except
子句之后。
else
子句将在 try
子句没有发生任何异常的时候执行。
以下实例在 try
语句中判断文件是否可以打开,如果打开文件时正常的没有发生异常则执行 else
部分的语句,读取文件内容:
# 正常打开
try:
with open('solution.py', 'r') as f:
line = f.read()
except:
print('出错了')
else:
print('没有出错了')
没有出错了
try-finally 语句
try-finally
语句无论是否发生异常都将执行最后的代码。
以下实例中 finally
语句无论异常是否发生都会执行:
try:
1/1
except:
print('出错了')
else:
print('没有出错了')
finally:
print('不管有没有出错,我100%会执行')
没有出错了
不管有没有出错,我100%会执行
抛出异常
首先要分清楚程序发生异常和程序执行错误,它们完全是两码事,程序由于错误导致的运行异常,是需要程序员想办法解决的;但还有一些异常,是程序正常运行的结果,比如用 raise
手动引发的异常。
raise
语句
raise [exceptionName [(reason)]]
其中,用 []
括起来的为可选参数,其作用是指定抛出的异常名称,以及异常信息的相关描述。如果可选参数全部省略,则 raise
会把当前错误原样抛出;如果仅省略 (reason)
,则在抛出异常时,将不附带任何的异常描述信息。
也就是说,raise
语句有如下三种常用的用法:
raise
:单独一个raise
。该语句引发当前上下文中捕获的异常(比如在except
块中),或默认引发RuntimeError
异常。raise
异常类名称:raise
后带一个异常类名称,表示引发执行类型的异常。raise
异常类名称(描述信息):在引发指定类型的异常的同时,附带异常的描述信息。
显然,每次执行 raise
语句,都只能引发一次执行的异常。首先,我们来测试一下以上 3 种 raise
的用法:
运行 rase
:
Traceback (most recent call last):
File "/code/main.py", line 2, in
import solution
File "/code/solution.py", line 1, in
raise
RuntimeError: No active exception to reraise
运行 raise ZeroDivisionError
:
Traceback (most recent call last):
File "/code/main.py", line 2, in
import solution
File "/code/solution.py", line 3, in
raise ZeroDivisionError
ZeroDivisionError
运行 raise ZeroDivisionError("除数不能为零")
:
Traceback (most recent call last):
File "/code/main.py", line 2, in
import solution
File "/code/solution.py", line 5, in
raise ZeroDivisionError("除数不能为零")
ZeroDivisionError: 除数不能为零
当然,我们手动让程序引发异常,很多时候并不是为了让其崩溃。事实上,raise
语句引发的异常通常用 try except (else finally)
异常处理结构来捕获并进行处理。例如:
try:
a = 'hello'
if(not a.isdigit()):
raise ValueError("a 必须是数字")
except ValueError as e:
print("引发异常:",repr(e))
引发异常: ValueError('a 必须是数字')
可以看到,当用字符串 a
不是数字时,程序会进入 if
判断语句,并执行 raise
引发 ValueError
异常。但由于其位于 try
块中,所以 raise
抛出的异常会被 try
捕获,并由 except
块进行处理。
因此,虽然程序中使用了 raise
语句引发异常,但程序的执行是正常的,手动抛出的异常并不会导致程序崩溃
正如前面所看到的,在使用 raise
语句时可以不带参数,例如:
try:
a = 'hello'
if(not a.isdigit()):
raise ValueError("a 必须是数字")
except ValueError as e:
print("引发异常:",repr(e))
raise
Traceback (most recent call last):
File "/code/main.py", line 2, in
import solution
File "/code/solution.py", line 4, in
raise ValueError("a 必须是数字")
ValueError: a 必须是数字
这里重点关注位于 except
块中的 raise
,由于在其之前我们已经手动引发了 ValueError
异常,因此这里当再使用 raise
语句时,它会再次引发一次。
当在没有引发过异常的程序使用无参的 raise
语句时,它默认引发的是 RuntimeError
异常。例如:
try:
a = 'hello'
if(not a.isdigit()):
raise
except ValueError as e:
print("引发异常:",repr(e))
Traceback (most recent call last):
File "/code/main.py", line 2, in
import solution
File "/code/solution.py", line 4, in
raise
RuntimeError: No active exception to reraise
内置异常
在 Python 中,所有异常必须为一个派生自 BaseException
的类的实例。 在带有提及一个特定类的 except
子句的 try
语句中,该子句也会处理任何派生自该类的异常类(但不处理该子句本身所派生出的异常类)。通过子类化创建的两个不相关异常类永远是不等效的,既使它们具有相同的名称。
下面列出的内置异常可通过解释器或内置函数来生成。除非另有说明,它们都会具有一个提示导致错误详细原因的“关联值”。这可以是一个字符串或由多个信息项(例如一个错误码和一个解释错误的字符串)组成的元组。关联值通常会作为参数被传递给异常类的构造器。
用户代码可以引发内置异常。这可以被用于测试异常处理程序或报告错误条件,“就像” 在解释器引发了相同异常的情况时一样;但是请注意,没有任何机制能防止用户代码引发不适当的错误。
内置异常类可以被子类化以定义新的异常;鼓励程序员从 Exception
类或它的某个子类而不是从 BaseException
来派生新的异常。
当在 except
或 finally
子句中引发(或重新引发)异常时,__context__
会被自动设为所捕获的最后一个异常;如果新的异常未被处理,则最终显示的回溯信息将包括原始的异常和最后的异常。
当引发一个新的异常(而不是简单地使用 raise
来重新引发当前在处理的异常)时,隐式的异常上下文可以通过使用带有 raise
的 from
来补充一个显式的原因:
raise new_exc from original_exc
跟在 from
之后的表达式必须为一个异常或 None
。它将在所引发的异常上被设置为 __cause__
。设置 __cause__
还会隐式地将 __suppress_context__
属性设为 True
,这样使用 raise new_exc from None
可以有效地将旧异常替换为新异常来显示其目的 (例如将 KeyError
转换为 AttributeError
),同时让旧异常在 __context__
中保持可用状态以便在调试时进行内省。
除了异常本身的回溯以外,默认的回溯还会显示这些串连的异常。__cause__
中的显式串连异常如果存在将总是显示。__context__
中的隐式串连异常仅在 __cause__
为 None
并且 __suppress_context__
为假值时显示。
不论在哪种情况下,异常本身总会在任何串连异常之后显示,以便回溯的最后一行总是显示所引发的最后一个异常。
内置异常的类层级结构如下:
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
异常的基类
下列异常主要被用作其他异常的基类。
- exception
BaseException
所有内置异常的基类。它不应该被用户自定义类直接继承(这种情况请使用 Exception
)。如果在此类的实例上调用 str()
,则会返回实例的参数表示,或者当没有参数时返回空字符串。args
传给异常构造器的参数元组。某些内置异常(例如 OSError
)接受特定数量的参数并赋予此元组中的元素特殊的含义,而其他异常通常只接受一个给出错误信息的单独字符串。with_traceback(tb)
此方法将 tb
设为异常的新回溯信息并返回该异常对象。它通常以如下的形式在异常处理程序中使用:
try:
...
except SomeException:
tb = sys.exc_info()[2]
raise OtherException(...).with_traceback(tb)
- exception
Exception
所有内置的非系统退出类异常都派生自此类。所有用户自定义异常也应当派生自此类。
- exception
ArithmeticError
此基类用于派生针对各种算术类错误而引发的内置异常: OverflowError
, ZeroDivisionError
, FloatingPointError
。
- exception
BufferError
当与缓冲区相关的操作无法执行时将被引发。
- exception
LookupError
此基类用于派生当映射或序列所使用的键或索引无效时引发的异常: IndexError
, KeyError
。这可以通过 codecs.lookup()
来直接引发。