Python 异常处理
Python 异常处理
Section titled “Python 异常处理”程序中错误是不可避免的。Python 提供了强大的机制来优雅地处理运行时错误,防止程序意外崩溃。
- 异常处理: 使用
try、except、else和finally块来管理发生时的错误。 - 断言: 在开发和调试期间使用
assert语句进行内部自检。
什么是异常?
Section titled “什么是异常?”异常(Exception)是程序执行期间发生的、中断正常指令流的事件。当 Python 遇到错误情况(例如除以零或尝试访问不存在的文件)时,它会创建一个异常对象。
如果你的代码没有“捕获”并处理这个异常对象,程序就会终止并打印回溯信息(traceback,一个显示错误发生位置的错误消息)。
常见的内置异常
Section titled “常见的内置异常”Python 有许多内置异常用于处理常见的错误。理解这些异常有助于编写特定的 except 块。以下是一些示例:
| 异常名称 | 描述 |
|---|---|
Exception | 大多数内置的、非系统退出异常的基类。 |
AttributeError | 当属性引用或赋值失败时引发(例如,mylist.non_existent_method())。 |
ImportError | 当 import 语句无法找到模块或模块内的名称时引发。 |
ModuleNotFoundError | ImportError 的子类,当找不到模块时引发。 |
IndexError | 当序列下标(索引)超出范围时引发(例如,列表只有 3 个项时访问 mylist[10])。 |
KeyError | 当字典键未找到时引发(例如,mydict['non_existent_key'])。 |
NameError | 当找不到局部或全局名称(变量、函数)时引发。 |
TypeError | 当对不适当类型的对象执行操作或调用函数时引发(例如,'2' + 2)。 |
ValueError | 当操作或函数接收到正确类型但值不适当的参数时引发(例如,int('abc'))。 |
ZeroDivisionError | 当除法或取模运算的第二个参数为零时引发。 |
FileNotFoundError | OSError 的子类,当尝试打开不存在的文件时引发。 |
OSError | 操作系统相关错误(如文件未找到、权限被拒绝)的基类。 |
KeyboardInterrupt | 当用户按下中断键(通常是 Ctrl+C)时引发。 |
处理异常:try 和 except
Section titled “处理异常:try 和 except”为了处理潜在错误,你可以将可能引发异常的代码放在 try 块中。然后紧跟一个或多个 except 块来指定如何处理不同类型的异常。
try: # 可能引发异常的代码 # ... 操作 ... result = 10 / risky_variable file = open("maybe_missing.txt", "r") # ... 更多操作 ...except ZeroDivisionError: # 特别处理除以零的错误 print("Error: Cannot divide by zero!") result = 0except 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中,从而可以访问错误的详细信息。
else 子句
Section titled “else 子句”你可以在所有 except 块之后添加一个可选的 else 块。else 块中的代码只有在 try 块完成且没有引发任何异常时才会执行。
try: # 可能失败的操作 value = int(input("Enter a number: ")) result = 100 / valueexcept 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 子句
Section titled “finally 子句”可以添加一个可选的 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 / 0except 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 中使用。
用户自定义异常
Section titled “用户自定义异常”对于特定于你的应用或库的错误,可以通过继承 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 = 100withdrawal_amount = 150
try: if withdrawal_amount > account_balance: raise InsufficientFundsError(account_balance, withdrawal_amount) else: print("Withdrawal successful.") # account_balance -= withdrawal_amountexcept InsufficientFundsError as e: print(f"Transaction Failed: {e}") # 如果需要,访问自定义属性: # print(f"当前余额:{e.balance},所需金额:{e.required}")自定义异常使你的错误处理更具针对性和信息量。
Python 中的断言
Section titled “Python 中的断言”断言(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)}")
# 无效调用 - 将引发 AssertionErrortry: print(calculate_discount(100, 110))except AssertionError as e: print(f"Assertion Failed: {e}")重要提示:在 Python 中,可以通过使用 -O(优化)标志运行解释器来全局禁用断言(例如 python -O your_script.py)。因此,断言不应用于必须始终存在的错误处理逻辑。