8. 错误异常

至此教程深入介绍错误信息如果尝试教程中的例子应该已经看到一些错误信息错误至少分为语法错误 异常
8.1.
语法错误

语法错误又称解析错误学习 Python 常见错误
>>>

while True print('Hello world')
File "<stdin>", line 1
while True print('Hello world')
^^^^^
SyntaxError: invalid syntax

The parser repeats the offending line and displays little arrows pointing at the place where the error was detected. Note that this is not always the place that needs to be fixed. In the example, the error is detected at the function print(), since a colon (':') is missing just before it.

The file name (<stdin> in our example) and line number are printed so you know where to look in case the input came from a file.
8.2.
异常

即使语句表达式使用正确语法执行可能触发错误执行检测错误称为 异常异常不一定导致严重后果我们学会如何处理 Python 异常大多数异常不会程序处理而是显示下列错误信息
>>>

10 * (1/0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
10 * (1/0)
~^~
ZeroDivisionError: division by zero

4 + spam*3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
4 + spam*3
^^^^
NameError: name 'spam' is not defined

'2' + 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
'2' + 2
~~~~^~~
TypeError: can only concatenate str (not "int") to str

错误信息最后一行说明程序到了什么类型错误异常不同类型类型名称作为错误信息一部分打印出来上述示例中的异常类型依次:ZeroDivisionError, NameError TypeError。作为异常类型打印字符串发生内置异常名称对于所有内置异常如此对于用户定义异常不一定如此虽然这种规范有用)。标准异常类型内置标识不是保留关键字)。

其余部分根据异常类型结合出错原因说明错误细节

错误信息开头堆栈回溯形式展示发生异常语境一般列出源代码堆栈回溯不会显示标准输入读取

内置异常 列出内置异常及其含义
8.3.
异常处理

可以编写程序处理选定异常例会要求用户一直输入内容直到输入有效整数允许用户中断程序使用 Control-C 操作系统支持其他操作);注意用户中断程序触发 KeyboardInterrupt 异常
>>>

while True:

try:

x = int(input("Please enter a number: "))

break

except ValueError:

print("Oops! That was no valid number. Try again...")


try
语句工作原理如下

首先执行 try 子句 (try except 关键字之间语句)。

如果没有触发异常跳过 except 子句,try 语句执行完毕

如果执行 try 子句发生异常跳过子句剩下部分如果异常类型 except 关键字指定异常匹配执行 except 子句然后 try/except 代码之后继续执行

如果发生异常 except 子句 指定异常匹配传递外层 try 语句如果没有找到处理器 未处理异常 执行停止输出错误消息

try
语句可以多个 except 子句 不同异常指定处理程序最多只有处理程序执行处理程序处理对应 try 子句 发生异常处理同一 try 语句其他处理程序中的异常。 except 子句 可以圆括号元组指定多个异常例如:

... except (RuntimeError, TypeError, NameError):
... pass

except 子句中的匹配异常本身实例其所派生实例反过来不可以 --- 列出派生 except 子句 不会匹配实例)。 例如下面代码依次打印 B, C, D:

class B(Exception):
pass

class C(B):
pass

class D(C):
pass

for cls in [B, C, D]:
try:
raise cls()
except D:
print("D")
except C:
print("C")
except B:
print("B")

注意如果颠倒 except 子句 顺序 except B ),输出 B, B, B --- 触发第一匹配 except 子句

发生异常可能具有关联异常 参数是否需要参数以及参数类型取决于异常类型

except
子句 可能异常名称后面指定变量这个变量绑定异常实例实例通常存储参数 args 属性为了方便起见内置异常类型定义 __str__() 打印所有参数不必访问 .args。
>>>

try:

raise Exception('spam', 'eggs')

except Exception as inst:

print(type(inst)) #
异常类型

print(inst.args) #
参数存在 .args

print(inst) # __str__
允许 args 直接打印

#
可能异常覆盖

x, y = inst.args #
解包 args

print('x =', x)

print('y =', y)


<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs

未处理异常 __str__() 输出打印异常消息最后部分 ('detail')。

BaseException
所有异常共同个子, Exception ,所有非致命异常不是 Exception 异常通常处理因为它们用来指示程序应该终止它们包括 sys.exit() 引发 SystemExit ,以及用户希望中断程序引发 KeyboardInterrupt 。

Exception
可以用作通配符捕获几乎一切然而做法尽可能具体说明我们打算处理异常类型允许任何意外异常传播下去

处理 Exception 常见模式打印记录异常然后重新提出允许调用处理异常):

import sys

try:
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())
except OSError as err:
print("OS error:", err)
except ValueError:
print("Could not convert data to an integer.")
except Exception as err:
print(f"Unexpected {err=}, {type(err)=}")
raise

try ... except
语句具有可选 else 子句子句如果存在必须所有 except 子句 之后适用 try 子句 没有引发异常必须执行代码例如:

for arg in sys.argv[1:]:
try:
f = open(arg, 'r')
except OSError:
print('cannot open', arg)
else:
print(arg, 'has', len(f.readlines()), 'lines')
f.close()

使用 else 子句 try 子句添加额外代码要好可以避免意外捕获 try ... except 语句保护代码触发异常

异常处理程序不仅处理 try 子句 立刻发生异常处理 try 子句 调用包括间接调用函数例如:
>>>

def this_fails():

x = 1/0


try:

this_fails()

except ZeroDivisionError as err:

print('Handling run-time error:', err)


Handling run-time error: division by zero

8.4.
触发异常

raise
语句支持强制触发指定异常例如
>>>

raise NameError('HiThere')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
raise NameError('HiThere')
NameError: HiThere

raise
唯一参数就是触发异常这个参数必须异常实例异常派生 BaseException 例如 Exception )。如果传递异常通过调用没有参数构造函数实例

raise ValueError # 'raise ValueError()'
简化

如果判断是否触发异常并不打算处理异常可以使用简单 raise 语句重新触发异常
>>>

try:

raise NameError('HiThere')

except NameError:

print('An exception flew by!')

raise


An exception flew by!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
raise NameError('HiThere')
NameError: HiThere

8.5.
异常

如果未处理异常发生 except 部分将会处理异常附加上面包括错误信息:
>>>

try:

open("database.sqlite")

except OSError:

raise RuntimeError("unable to handle error")


Traceback (most recent call last):
File "<stdin>", line 2, in <module>
open("database.sqlite")
~~~~^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'database.sqlite'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "<stdin>", line 4, in <module>
raise RuntimeError("unable to handle error")
RuntimeError: unable to handle error

为了表明异常另一异常直接后果, raise 语句允许可选 from 子句:

# exc
必须异常实例 None。
raise RuntimeError from exc

转换异常这种方式有用例如
>>>

def func():

raise ConnectionError


try:

func()

except ConnectionError as exc:

raise RuntimeError('Failed to open database') from exc


Traceback (most recent call last):
File "<stdin>", line 2, in <module>
func()
~~~~^^
File "<stdin>", line 2, in func
ConnectionError

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "<stdin>", line 4, in <module>
raise RuntimeError('Failed to open database') from exc
RuntimeError: Failed to open database

允许使用 from None 表达禁用自动异常:
>>>

try:

open('database.sqlite')

except OSError:

raise RuntimeError from None


Traceback (most recent call last):
File "<stdin>", line 4, in <module>
raise RuntimeError from None
RuntimeError

异常机制详见 内置异常
8.6.
用户自定义异常

程序可以通过创建异常命名自己异常(Python 内容详见 )。不论是以直接还是间接方式异常应从 Exception 派生

异常可以定义其他所能任何通常应当保持简单往往提供一些属性允许相应异常处理程序提取有关错误信息

大多数异常命名 “Error” 结尾类似标准异常命名

许多标准模块定义自己异常报告他们定义函数可能出现错误
8.7.
定义清理操作

try
语句还有可选子句用于定义所有情况必须执行清理操作例如
>>>

try:

raise KeyboardInterrupt

finally:

print('Goodbye, world!')


Goodbye, world!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
raise KeyboardInterrupt
KeyboardInterrupt

如果存在 finally 子句 finally 子句 try 语句结束执行最后任务不论 try 语句是否触发异常都会执行 finally 子句以下内容介绍比较复杂触发异常情景

如果执行 try 子句期间触发异常 except 子句处理异常如果异常没有 except 子句处理 finally 子句执行重新触发

except
else 子句执行期间触发异常同样异常 finally 子句执行之后重新触发

如果 finally 子句包含 break、continue return 语句异常不会重新引发

如果执行 try 语句遇到 break,、continue return 语句 finally 子句执行 break、continue return 语句之前执行

如果 finally 子句包含 return 语句返回来自 finally 子句 return 语句返回不是来自 try 子句 return 语句返回

例如
>>>

def bool_return():

try:

return True

finally:

return False


bool_return()
False

比较复杂例子
>>>

def divide(x, y):

try:

result = x / y

except ZeroDivisionError:

print("division by zero!")

else:

print("result is", result)

finally:

print("executing finally clause")


divide(2, 1)
result is 2.0
executing finally clause

divide(2, 0)
division by zero!
executing finally clause

divide("2", "1")
executing finally clause
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
divide("2", "1")
~~~~~~^^^^^^^^^^
File "<stdin>", line 3, in divide
result = x / y
~~^~~
TypeError: unsupported operand type(s) for /: 'str' and 'str'

任何情况都会执行 finally 子句。except 子句处理字符串触发 TypeError,因此 finally 子句执行重新触发

实际应用程序,finally 子句对于释放外部资源例如文件或者网络连接非常有用无论是否成功使用资源
8.8.
预定义清理操作

某些对象定义需要对象执行标准清理操作无论使用对象操作是否成功都会执行清理操作比如打开文件输出文件内容

for line in open("myfile.txt"):
print(line, end="")

这个代码问题在于执行代码文件确定时间处于打开状态简单脚本没有问题对于较大应用程序可能出问题。with 语句支持以及正确清理方式使用文件对象

with open("myfile.txt") as f:
for line in f:
print(line, end="")

语句执行完毕即使处理遇到问题都会关闭文件 f。文件一样支持预定义清理操作对象文档指出一点
8.9.
引发处理多个相关异常

有些情况必要报告几个已经发生异常通常并发框架几个任务并行失败情况其他有时需要继续执行收集多个错误不是引发第一异常

内置 ExceptionGroup 打包异常实例列表这样它们可以一起引发它本身就是异常所以可以其他异常一样被捕
>>>

def f():

excs = [OSError('error 1'), SystemError('error 2')]

raise ExceptionGroup('there were problems', excs)


f()
+ Exception Group Traceback (most recent call last):
| File "<stdin>", line 1, in <module>
| f()
| ~^^
| File "<stdin>", line 3, in f
| raise ExceptionGroup('there were problems', excs)
| ExceptionGroup: there were problems (2 sub-exceptions)
+-+---------------- 1 ----------------
| OSError: error 1
+---------------- 2 ----------------
| SystemError: error 2
+------------------------------------

try:

f()

except Exception as e:

print(f'caught {type(e)}: e')


caught <class 'ExceptionGroup'>: e


通过使用 except* 代替 except ,我们可以选择处理符合某种类型异常在下面的例子显示嵌套异常 except* 子句提取某种类型异常所有其他异常传播其他子句最终重新引发
>>>

def f():

raise ExceptionGroup(

"group1",

[

OSError(1),

SystemError(2),

ExceptionGroup(

"group2",

[

OSError(3),

RecursionError(4)

]

)

]

)


try:

f()

except* OSError as e:

print("There were OSErrors")

except* SystemError as e:

print("There were SystemErrors")


There were OSErrors
There were SystemErrors
+ Exception Group Traceback (most recent call last):
| File "<stdin>", line 2, in <module>
| f()
| ~^^
| File "<stdin>", line 2, in f
| raise ExceptionGroup(
| ...<12 lines>...
| )
| ExceptionGroup: group1 (1 sub-exception)
+-+---------------- 1 ----------------
| ExceptionGroup: group2 (1 sub-exception)
+-+---------------- 1 ----------------
| RecursionError: 4
+------------------------------------


注意嵌套异常中的异常必须实例不是类型因为实践这些异常通常那些已经程序提出捕获异常模式如下:
>>>

excs = []

for test in tests:

try:

test.run()

except Exception as e:

excs.append(e)


if excs:

raise ExceptionGroup("Test Failures", excs)


8.10.
注释细化异常情况

异常创建引发通常初始化描述发生错误信息有些情况异常被捕添加信息有用为了这个目的异常 add_note(note) 方法接受字符串添加异常注释列表标准回溯异常之后按照它们添加顺序呈现包括所有注释
>>>

try:

raise TypeError('bad type')

except Exception as e:

e.add_note('Add some information')

e.add_note('Add some more information')

raise


Traceback (most recent call last):
File "<stdin>", line 2, in <module>
raise TypeError('bad type')
TypeError: bad type
Add some information
Add some more information


例如异常收集异常我们可能各个错误添加上下文信息在下中的异常说明指出这个错误什么时候发生
>>>

def f():

raise OSError('operation failed')


excs = []

for i in range(3):

try:

f()

except Exception as e:

e.add_note(f'Happened in Iteration {i+1}')

excs.append(e)


raise ExceptionGroup('We have some problems', excs)
+ Exception Group Traceback (most recent call last):
| File "<stdin>", line 1, in <module>
| raise ExceptionGroup('We have some problems', excs)
| ExceptionGroup: We have some problems (3 sub-exceptions)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "<stdin>", line 3, in <module>
| f()
| ~^^
| File "<stdin>", line 2, in f
| raise OSError('operation failed')
| OSError: operation failed
| Happened in Iteration 1
+---------------- 2 ----------------
| Traceback (most recent call last):
| File "<stdin>", line 3, in <module>
| f()
| ~^^
| File "<stdin>", line 2, in f
| raise OSError('operation failed')
| OSError: operation failed
| Happened in Iteration 2
+---------------- 3 ----------------
| Traceback (most recent call last):
| File "<stdin>", line 3, in <module>
| f()
| ~^^
| File "<stdin>", line 2, in f
| raise OSError('operation failed')
| OSError: operation failed
| Happened in Iteration 3
+------------------------------------