【Python】8 错误和异常
8. 错误和异常
至此,本教程还未深入介绍错误信息,但如果你尝试过本教程前文的例子,应该已经看到过一些错误信息。
错误可(至少)被分为两种:语法错误 和 异常。
8.1. 语法错误
语法错误(解析错误)是学习 Python 时最常见的错误,例如:
while True print('Hello world')
解释器会重复出错的行并显示指向检测到错误的位置的小箭头,并打印文件名和行号。
例如:
File "<stdin>", line 1
while True print('Hello world')
^^^^^
SyntaxError: invalid syntax
注意
箭头指示的位置不一定就是需要修复的位置(此例中实际是缺少冒号:)。
8.2. 异常
即使语法正确,执行时仍可能触发错误,执行时检测到的错误称为异常。
大多数异常如果未被处理,会显示类似的错误信息,例如:
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)。
标准异常类型是内置标识符(非关键字)。
未处理异常的 __str__() 输出会被打印为异常消息的最后部分(“detail”)。
内置异常基类为 BaseException,大多数非致命异常继承自 Exception。
8.3. 异常的处理
可以编写程序处理选定的异常。
例如,反复要求用户输入直到输入有效整数:
while True:
try:
x = int(input("Please enter a number: "))
break
except ValueError:
print("Oops! That was no valid number. Try again...")
try 语句工作流程概述:
- 执行
try子句内容。 - 若无异常,跳过
except,继续执行。 - 若发生异常,跳过
try中剩余部分,尝试与except指定的异常匹配,
匹配则执行该except,然后继续执行try/except之后的代码。 - 未匹配的异常将向外层传播,最终成为未处理异常导致程序停止。
可以有多个 except 子句(最多执行一个)。
可以用元组在同一 except 中列出多个异常:
except (RuntimeError, TypeError, NameError):
pass
异常匹配遵循继承关系;
捕获顺序重要(先匹配的先处理)。
异常可以带参数;except Exception as inst: 可以将异常实例绑定到变量 inst,并读取 inst.args。
示例:
try:
raise Exception('spam', 'eggs')
except Exception as inst:
print(type(inst))
print(inst.args)
print(inst)
x, y = inst.args
print('x =', x)
print('y =', y)
常见最佳实践:尽量具体地捕获异常,必要时记录并重新抛出。
示例:
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 子句,当 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()
异常处理也会覆盖在 try 中调用(或间接调用)的函数中发生的异常,例如:
def this_fails():
x = 1/0
try:
this_fails()
except ZeroDivisionError as err:
print('Handling run-time error:', err)
8.4. 触发异常
使用 raise 强制触发指定异常:
raise NameError('HiThere')
输出:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
raise NameError('HiThere')
NameError: HiThere
raise 的参数必须是异常实例或异常类(派生自 BaseException)。
如果只想重新抛出当前异常,可用不带参数的 raise:
try:
raise NameError('HiThere')
except NameError:
print('An exception flew by!')
raise
8.5. 异常链
如果在 except 中发生未处理异常,错误信息会显示被处理异常的上下文。
可以用 from 指示直接原因:
# exc 必须为异常实例或 None
raise RuntimeError from exc
示例将一个 ConnectionError 转换为 RuntimeError:
def func():
raise ConnectionError
try:
func()
except ConnectionError as exc:
raise RuntimeError('Failed to open database') from exc
也可以用 from None 禁用自动异常链:
try:
open('database.sqlite')
except OSError:
raise RuntimeError from None
8.6. 用户自定义异常
自定义异常应从 Exception 派生。
异常类可以包含属性以便处理器提取信息。
大多数异常名以 “Error” 结尾。
8.7. 定义清理操作
try 还有 finally 子句,用于定义无论是否发生异常都要执行的清理操作:
try:
raise KeyboardInterrupt
finally:
print('Goodbye, world!')
输出:
Goodbye, world!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
raise KeyboardInterrupt
KeyboardInterrupt
若 finally 存在,它总是在 try 语句结束前执行。
复杂情形要点:
- 若
try中发生异常且未被except处理,finally执行后异常会重新抛出。 except或else中发生异常也会在finally执行后重新抛出。- 若
finally中执行break、continue或return,则异常不会重新抛出(从 Python 3.14 开始对此会发出SyntaxWarning)。 finally中的return会覆盖try中的返回值(不推荐,3.14 起有警告)。
示例:
def bool_return():
try:
return True
finally:
return False
print(bool_return()) # False
另一个例子说明 except、else、finally 的交互:
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)
divide(2, 0)
divide("2", "1")
输出片段:
result is 2.0
executing finally clause
division by zero!
executing finally clause
executing finally clause
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for /: 'str' and 'str'
finally 常用于释放外部资源(文件、网络连接等)。
8.8. 预定义的清理操作
某些对象定义了预定义清理操作,例如文件对象。
与直接使用 open() 迭代相比,with 语句能确保及时正确的清理:
for line in open("myfile.txt"):
print(line, end="")
此写法可能导致文件较长时间未关闭,推荐使用:
with open("myfile.txt") as f:
for line in f:
print(line, end="")
with 语句在语句结束后总会关闭文件(即使处理中出现异常)。
8.9. 引发和处理多个不相关的异常
在并发或需要收集多个错误的情况下,可使用 ExceptionGroup 打包多个异常并一起引发:
def f():
excs = [OSError('error 1'), SystemError('error 2')]
raise ExceptionGroup('there were problems', excs)
f()
示例输出(简化)将显示 ExceptionGroup 及其子异常。
使用 except* 可以选择性地处理异常组中匹配的异常类型:
def f():
excs = [OSError('error 1'), SystemError('error 2')]
raise ExceptionGroup('there were problems', excs)
try:
f()
except* OSError as e:
print("There were OSErrors")
except* SystemError as e:
print("There were SystemErrors")
注意
嵌套在异常组中的异常必须是实例(通常是已被提出并捕获的异常实例)。
示例收集多个测试错误后抛出组:
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
更实用的用法:当把异常收集到异常组中时,为每个异常添加上下文说明:
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)
输出将显示每个子异常及其对应的注释(例如 “Happened in Iteration 1/2/3”)。
更多推荐




所有评论(0)