Skip to content

Python 异常处理

程序中错误是不可避免的。Python 提供了强大的机制来优雅地处理运行时错误,防止程序意外崩溃。

  • 异常处理: 使用 try、except、else 和 finally 块来管理发生时的错误。
  • 断言: 在开发和调试期间使用 assert 语句进行内部自检。

异常(Exception)是程序执行期间发生的、中断正常指令流的事件。当 Python 遇到错误情况(例如除以零或尝试访问不存在的文件)时,它会创建一个异常对象。

如果你的代码没有“捕获”并处理这个异常对象,程序就会终止并打印回溯信息(traceback,一个显示错误发生位置的错误消息)。

Python 有许多内置异常用于处理常见的错误。理解这些异常有助于编写特定的 except 块。以下是一些示例:

异常名称描述
Exception大多数内置的、非系统退出异常的基类。
AttributeError当属性引用或赋值失败时引发(例如,mylist.non_existent_method())。
ImportError当 import 语句无法找到模块或模块内的名称时引发。
ModuleNotFoundErrorImportError 的子类,当找不到模块时引发。
IndexError当序列下标(索引)超出范围时引发(例如,列表只有 3 个项时访问 mylist[10])。
KeyError当字典键未找到时引发(例如,mydict['non_existent_key'])。
NameError当找不到局部或全局名称(变量、函数)时引发。
TypeError当对不适当类型的对象执行操作或调用函数时引发(例如,'2' + 2)。
ValueError当操作或函数接收到正确类型但值不适当的参数时引发(例如,int('abc'))。
ZeroDivisionError当除法或取模运算的第二个参数为零时引发。
FileNotFoundErrorOSError 的子类,当尝试打开不存在的文件时引发。
OSError操作系统相关错误(如文件未找到、权限被拒绝)的基类。
KeyboardInterrupt当用户按下中断键(通常是 Ctrl+C)时引发。

为了处理潜在错误,你可以将可能引发异常的代码放在 try 块中。然后紧跟一个或多个 except 块来指定如何处理不同类型的异常。

try:
# 可能引发异常的代码
# ... 操作 ...
result = 10 / risky_variable
file = open("maybe_missing.txt", "r")
# ... 更多操作 ...
except ZeroDivisionError:
# 特别处理除以零的错误
print("Error: Cannot divide by zero!")
result = 0
except FileNotFoundError:
# 特别处理文件未找到的错误
print("Error: File not found!")
# 可以提供默认值或退出
except Exception as e:
# 捕获所有继承自 Exception 的其他异常
print(f"An unexpected error occurred: {e}")
# 记录错误、提供通用消息等
# 如果捕获到异常,程序会继续在此处执行

要点:

  • Python 会尝试执行 try 块中的代码。
  • 如果发生异常,Python 会停止执行 try 块,并查找匹配的 except 块。
  • 它按顺序检查 except 块。第一个类型与引发的异常匹配(或为其基类)的块将被执行。
  • 如果找到了匹配的 except 块并执行,程序将在 try...except 结构之后继续执行(除非 except 块本身再次引发异常或退出)。
  • 如果找不到匹配的 except 块,异常会沿着调用栈向上层传递。如果最终未被捕获,程序将终止。
  • 通常使用特定的异常类型(如 ZeroDivisionError)比使用广泛的异常类型(如 Exception)更好,因为它允许有针对性地处理错误,并避免意外捕获不相关的错误。
  • as e 子句将异常对象捕获到变量 e 中,从而可以访问错误的详细信息。

你可以在所有 except 块之后添加一个可选的 else 块。else 块中的代码只有在 try 块完成且没有引发任何异常时才会执行。

try:
# 可能失败的操作
value = int(input("Enter a number: "))
result = 100 / value
except ValueError:
print("Invalid input. Please enter a number.")
except ZeroDivisionError:
print("Cannot divide by zero.")
else:
# 仅在 try 块成功时执行
print(f"Result is: {result}")
print("Operation successful.")
print("Program continues...")

else 块对于只应在 try 块成功时运行的代码很有用,将其与可能存在风险的代码分开。

可以添加一个可选的 finally 块。finally 块中的代码总是执行,无论 try 块中是否发生异常,也无论异常是否被 except 块处理。即使 try 或 except 块使用了 return、break 或 continue,它也会执行。

file = None # 在 try 块外部初始化文件变量
try:
print("Attempting to open file...")
file = open("my_data.txt", "w")
file.write("Writing important data!")
# 模拟打开后可能发生的错误
# result = 10 / 0
except FileNotFoundError:
print("Error: Could not find the file path to write.")
except Exception as e:
print(f"An error occurred during writing: {e}")
finally:
# 此块总是用于清理
print("Entering finally block...")
if file and not file.closed:
print("Closing the file.")
file.close()
else:
print("File was not opened or already closed.")
print("Program finished.")

finally 通常用于清理操作,例如关闭文件或网络连接,确保资源得到妥善释放。

注意:一个 try 块必须至少有一个 except 或 finally 子句。它可以有 except 子句、finally 子句或两者都有。如果两者都存在,finally 子句最后执行。

如在 except Exception as e: 示例中所示,你可以捕获异常对象本身。这个对象通常包含有关错误的有用信息。

try:
x = int('not_a_number')
except ValueError as ve:
print(f"Caught a ValueError!")
print(f"Error details: {ve}") # 打印与异常相关的标准错误消息
print(f"Arguments: {ve.args}") # 异常存储参数,通常包括消息

这会产生如下输出:

Caught a ValueError!
Error details: invalid literal for int() with base 10: 'not_a_number'
Arguments: ("invalid literal for int() with base 10: 'not_a_number'",)

你也可以在自己的代码中使用 raise 语句特意引发异常。当你检测到某种错误情况,导致你的函数或方法无法履行其职责时,这会很有用。

raise ExceptionType("关于错误的描述性消息(可选)")
def check_positive(number):
if number <= 0:
# 引发一个特定的、适当的异常
raise ValueError("Input must be a positive number.")
print(f"{number} is positive.")
try:
check_positive(10)
check_positive(-5)
except ValueError as e:
print(f"Error caught: {e}")

最佳实践是在适当的时候引发特定的内置异常(如 ValueError、TypeError),或为应用特定的错误创建自定义异常。

注意:使用字符串引发异常(如 raise "Invalid level!")已弃用,不应在现代 Python 中使用。

对于特定于你的应用或库的错误,可以通过继承 Exception 或更特定的内置异常类来创建自定义异常类。

# 定义一个自定义异常类
class InsufficientFundsError(Exception):
"""当账户资金不足时引发的自定义异常"""
def __init__(self, balance, required):
self.balance = balance
self.required = required
message = f"Attempted to withdraw {required}, but only {balance} available."
super().__init__(message) # 调用基类构造函数
# 示例用法
account_balance = 100
withdrawal_amount = 150
try:
if withdrawal_amount > account_balance:
raise InsufficientFundsError(account_balance, withdrawal_amount)
else:
print("Withdrawal successful.")
# account_balance -= withdrawal_amount
except InsufficientFundsError as e:
print(f"Transaction Failed: {e}")
# 如果需要,访问自定义属性:
# print(f"当前余额:{e.balance},所需金额:{e.required}")

自定义异常使你的错误处理更具针对性和信息量。

断言(Assertion)是一种调试辅助工具,用于测试某个条件。如果条件为 True,则什么也不会发生。如果条件为 False,assert 语句将引发 AssertionError 异常,并带有一个可选消息。

断言主要用于检查内部错误或违反假设的情况(程序员错误),而不是用于处理预期的错误情况(如用户输入无效或文件丢失,这些情况应通过 try...except 处理)。

assert condition, "如果条件为 False 时的可选消息"
def calculate_discount(price, discount_percentage):
# 前置条件检查:确保输入对于逻辑有效
assert price >= 0, "Price cannot be negative"
assert 0 <= discount_percentage <= 100, "Discount must be between 0 and 100"
discount_amount = price * (discount_percentage / 100)
final_price = price - discount_amount
# 后置条件检查:确保结果合理
assert 0 <= final_price <= price, "Final price calculation seems wrong"
return final_price
# 有效调用
print(f"Discounted price: {calculate_discount(100, 10)}")
# 无效调用 - 将引发 AssertionError
try:
print(calculate_discount(100, 110))
except AssertionError as e:
print(f"Assertion Failed: {e}")

重要提示:在 Python 中,可以通过使用 -O(优化)标志运行解释器来全局禁用断言(例如 python -O your_script.py)。因此,断言不应用于必须始终存在的错误处理逻辑。