Python 3 - 异常
Python 3 - 异常处理
Section titled “Python 3 - 异常处理”错误在编程中是不可避免的。Python 提供了强大的机制来处理程序执行期间发生的错误。这些意外事件称为异常(exceptions)。
管理错误和进行调试(debugging)的两个关键特性是:
- 异常处理(Exception Handling): 使用
try、except、else和finally块来优雅地管理运行时错误(runtime errors)。 - 断言(Assertions): 在开发和测试期间使用
assert语句进行内部自检。
什么是异常?
Section titled “什么是异常?”异常是程序执行期间发生的一个事件,它中断了正常的指令流。当 Python 遇到无法处理的情况时(例如,除以零、访问不存在的文件、使用不正确的索引),它会*抛出(raise)*一个异常。
异常由一个 Python 对象表示,通常是派生自基类 Exception 的类的实例。如果抛出了异常而程序没有处理它,程序就会终止并打印一个追溯信息(traceback),它显示了导致错误的一系列调用。
常见的内置异常
Section titled “常见的内置异常”Python 为常见错误提供了许多内置异常。以下是一些经常遇到的异常:
| 异常名称 | 描述 |
|---|---|
Exception | 大多数用户自定义异常和内置异常的基类(不包括导致系统退出的异常)。 |
AttributeError | 在属性引用或赋值失败时抛出(例如,obj.nonexistent_method())。 |
EOFError | 当 input() 函数在读取任何数据之前达到文件末尾(end-of-file)条件时抛出。 |
ImportError | 当 import 语句未能找到模块或模块内的名称时抛出。 |
IndexError | 当序列下标(index)超出范围时抛出(例如,对于包含 5 个元素的列表,访问 my_list[10])。 |
KeyError | 当字典键(dictionary key)未找到时抛出(例如,my_dict['nonexistent_key'])。 |
KeyboardInterrupt | 当用户按下中断键(通常是 Ctrl+C)时抛出。 |
NameError | 当找不到本地或全局名称(变量 variable、函数 function)时抛出。 |
OSError | 与操作系统相关的错误的基类(例如,文件未找到、权限被拒绝)。其子类(如 FileNotFoundError、PermissionError)提供更具体的信息。 |
SyntaxError | 解析器在遇到语法错误时抛出。 |
IndentationError | SyntaxError 的子类。在缩进不正确时抛出。 |
TypeError | 当对类型不适当的对象执行操作或调用函数时抛出(例如,'hello' + 5)。 |
ValueError | 当操作或函数接收到类型正确但值不适当的参数时抛出(例如,int('abc'))。 |
ZeroDivisionError | 当除法或取模运算的第二个参数为零时抛出。 |
处理异常:try 和 except
Section titled “处理异常:try 和 except”要优雅地处理异常,你可以将可能引发异常的代码放在 try 块中。在 try 块之后,添加一个或多个 except 块来指定如何处理特定类型的异常。
try: # 可能抛出异常的代码 # ... 可疑操作在这里 ... passexcept SpecificExceptionType1: # 如果发生 SpecificExceptionType1 异常时运行的代码 passexcept (SpecificExceptionType2, SpecificExceptionType3): # 如果发生 SpecificExceptionType2 或 Type3 异常时运行的代码 passexcept AnotherExceptionType as e: # 如果发生 AnotherExceptionType 异常时运行的代码 # 'e' 变量持有异常对象(包含详细信息) print(f"An error occurred: {e}") # 发生了错误: {e}except Exception as e: # 捕获任何其他派生自 Exception 的异常(谨慎使用) print(f"An unexpected error occurred: {e}") # 发生了意料之外的错误: {e}要点:
- Python 尝试执行
try块中的代码。 - 如果发生异常,Python 会查找匹配的
except块。 - 第一个匹配的
except块将被执行。 - 如果没有找到匹配的
except块,异常将沿着调用栈(call stack)向上层传播(可能终止程序)。 - 通常不建议使用裸的
except:(不指定异常类型),因为它会捕获所有异常,包括像SystemExit或KeyboardInterrupt这样的系统异常,这使得调试或停止程序变得困难。 - 捕获特定的异常类型更受欢迎。
示例:处理除以零和无效输入
Section titled “示例:处理除以零和无效输入”#!/usr/bin/env python3
try: numerator_str = input("Enter the numerator: ") # 输入分子: denominator_str = input("Enter the denominator: ") # 输入分母:
numerator = int(numerator_str) denominator = int(denominator_str)
result = numerator / denominator print(f"The result is: {result}") # 结果是: {result}
except ValueError: print("Error: Please enter valid integers for numerator and denominator.") # 错误:请为分子和分母输入有效的整数。except ZeroDivisionError: print("Error: Cannot divide by zero.") # 错误:不能除以零。except Exception as e: print(f"An unexpected error occurred: {e}") # 发生了意料之外的错误: {e}
print("Execution continues after the try-except block.") # 执行在 try-except 块后继续。尝试使用不同的输入运行此代码(例如,‘10’ 和 ‘2’,‘10’ 和 ‘0’,‘10’ 和 ‘abc’),看看不同的 except 块如何处理这些错误。
else 子句
Section titled “else 子句”你可以选择性地在 except 块之后包含一个 else 块。else 块中的代码仅在 try 块完成且没有抛出任何异常时执行。
这对于仅在 try 块成功时才应该运行的代码非常有用,将它与可能出错的代码分离开来。
try: # 高风险操作 risky_operation()except SomeError: print("Handling SomeError...") # 处理 SomeError...else: # 仅在 try 块中没有发生异常时运行 print("Operation successful, proceeding...") # 操作成功,继续进行... perform_next_step()示例:文件处理与 else
Section titled “示例:文件处理与 else”#!/usr/bin/env python3
filename = "my_data.txt"
try: # 使用 'with' 确保即使发生错误文件也会关闭 with open(filename, 'r') as f: content = f.read()except FileNotFoundError: print(f"Error: File '{filename}' not found.") # 错误:文件 '{filename}' 未找到。except OSError as e: print(f"Error reading file '{filename}': {e}") # 读取文件 '{filename}' 时出错: {e}else: # 仅在文件成功打开和读取后才处理内容 print(f"File '{filename}' read successfully.") # 文件 '{filename}' 读取成功。 # print(f"Content: {content[:50]}...") # 处理内容finally 子句
Section titled “finally 子句”可以添加一个可选的 finally 块。finally 块中的代码总是执行,无论 try 块中是否发生异常,或者异常是否被 except 块处理。即使 try 或 except 块使用了 return、break 或 continue,它也会运行。
finally 子句通常用于必须在所有情况下都发生的清理操作,例如关闭文件或网络连接、释放锁等。
try: # 操作,可能包括资源分配 f = open("important_file.txt", "w") f.write("Some data") # 一些数据 # 这里可能发生错误: result = 10 / 0except ZeroDivisionError: print("Caught division by zero.") # 捕获到除以零的错误。finally: # 这段清理代码总是运行 print("Ensuring file is closed.") # 确保文件已关闭。 if 'f' in locals() and not f.closed: f.close()注意: 虽然 finally 对于清理至关重要,但现代 Python 通常使用上下文管理器(context managers)(即 with 语句)来处理文件等资源,它们会自动处理清理,从而使得为特定目的显式使用 finally 块不那么常见。上面的示例演示了概念;对于文件 I/O,通常更推荐使用 with open(...)。
一个 try 语句可以有 except 子句和 finally 子句。顺序是 try -> except(s) -> else -> finally。
你可以使用 raise 语句故意抛出异常。当你自己的代码检测到错误条件或想在捕获异常后重新抛出它时,这非常有用。
# 抛出一个特定类型的异常并带有消息raise ValueError("Invalid input value provided") # 提供了无效的输入值
# 抛出一个异常类的实例error_obj = TypeError("Incompatible types") # 不兼容的类型raise error_obj
# 重新抛出最近一个活动的异常(常用于 except 块中)try: do_something()except Exception as e: print("Logging the error...") # 记录错误... # 执行一些日志记录或部分处理 raise # 重新抛出原始异常 'e'示例:验证输入
Section titled “示例:验证输入”#!/usr/bin/env python3
def calculate_area(width, height): if not isinstance(width, (int, float)) or not isinstance(height, (int, float)): raise TypeError("Width and height must be numeric.") # 宽度和高度必须是数字。 if width <= 0 or height <= 0: raise ValueError("Width and height must be positive.") # 宽度和高度必须是正数。 return width * height
try: area = calculate_area(10, 5) print(f"Area: {area}") # 面积: {area}
area = calculate_area(10, -5) # area = calculate_area(10, "abc") # 取消注释以测试 TypeError print(f"Area: {area}")
except (ValueError, TypeError) as e: print(f"Error calculating area: {e}") # 计算面积时出错: {e}输出:
Area: 50Error calculating area: Width and height must be positive.用户自定义异常
Section titled “用户自定义异常”你可以通过定义继承自 Exception(或更具体的内置异常类)的新类来创建自己的自定义异常类型。这使得你可以创建特定于你的应用程序或库的更有意义的错误类型。
#!/usr/bin/env python3
# 定义一个自定义异常类class InsufficientFundsError(Exception): """账户余额不足时的自定义异常。""" def __init__(self, balance, amount_needed): self.balance = balance self.amount_needed = amount_needed message = f"Attempted to withdraw {amount_needed}, but only {balance} available." super().__init__(message) # 调用基类 Exception 的构造函数
# 示例用法account_balance = 100 # 账户余额withdrawal_amount = 150 # 取款金额
try: if withdrawal_amount > account_balance: raise InsufficientFundsError(account_balance, withdrawal_amount) else: account_balance -= withdrawal_amount print(f"Withdrawal successful. New balance: {account_balance}") # 取款成功。新余额: {account_balance}except InsufficientFundsError as e: print(f"Transaction Failed: {e}") # 交易失败: {e} # 如果需要,可以访问自定义属性: # print(f"Current balance: {e.balance}, Amount needed: {e.amount_needed}") # 当前余额: {e.balance}, 所需金额: {e.amount_needed}输出:
Transaction Failed: Attempted to withdraw 150, but only 100 available.断言(assert)主要在开发和测试期间用作内部健全性检查(sanity checks)。它们验证在程序正常运行时应该始终为真的条件。
语法:assert condition, [optional_message]
- 如果
condition计算结果为True,断言不做任何事情。 - 如果
condition计算结果为False,Python 会抛出AssertionError异常并带有可选消息。 - 断言可以在全局范围内禁用(为了在生产环境中提高性能),方法是使用
-O(optimize)标志运行 Python。因此,不应将它们用于必须始终发生的输入验证(input validation)或错误处理。
#!/usr/bin/env python3
def calculate_discount(price, discount_percentage): assert 0 <= discount_percentage <= 100, "Discount percentage must be between 0 and 100." discount_amount = price * (discount_percentage / 100) return price - discount_amount
# 有效调用final_price = calculate_discount(50, 10)print(f"Final price (10% off): {final_price}") # 最终价格 (10% 折扣): {final_price}
# 无效调用 - 如果断言启用,将抛出 AssertionErrortry: final_price = calculate_discount(50, 110) print(f"Final price (110% off): {final_price}")except AssertionError as e: print(f"Assertion Failed: {e}") # 断言失败: {e}输出(当断言启用时):
Final price (10% off): 45.0Assertion Failed: Discount percentage must be between 0 and 100.