Skip to content

Python 3 - 异常

错误在编程中是不可避免的。Python 提供了强大的机制来处理程序执行期间发生的错误。这些意外事件称为异常(exceptions)。

管理错误和进行调试(debugging)的两个关键特性是:

  • 异常处理(Exception Handling): 使用 try、except、else 和 finally 块来优雅地管理运行时错误(runtime errors)。
  • 断言(Assertions): 在开发和测试期间使用 assert 语句进行内部自检。

异常是程序执行期间发生的一个事件,它中断了正常的指令流。当 Python 遇到无法处理的情况时(例如,除以零、访问不存在的文件、使用不正确的索引),它会*抛出(raise)*一个异常。

异常由一个 Python 对象表示,通常是派生自基类 Exception 的类的实例。如果抛出了异常而程序没有处理它,程序就会终止并打印一个追溯信息(traceback),它显示了导致错误的一系列调用。

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解析器在遇到语法错误时抛出。
IndentationErrorSyntaxError 的子类。在缩进不正确时抛出。
TypeError当对类型不适当的对象执行操作或调用函数时抛出(例如,'hello' + 5)。
ValueError当操作或函数接收到类型正确但值不适当的参数时抛出(例如,int('abc'))。
ZeroDivisionError当除法或取模运算的第二个参数为零时抛出。

要优雅地处理异常,你可以将可能引发异常的代码放在 try 块中。在 try 块之后,添加一个或多个 except 块来指定如何处理特定类型的异常。

try:
# 可能抛出异常的代码
# ... 可疑操作在这里 ...
pass
except SpecificExceptionType1:
# 如果发生 SpecificExceptionType1 异常时运行的代码
pass
except (SpecificExceptionType2, SpecificExceptionType3):
# 如果发生 SpecificExceptionType2 或 Type3 异常时运行的代码
pass
except 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 这样的系统异常,这使得调试或停止程序变得困难。
  • 捕获特定的异常类型更受欢迎。
#!/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 块如何处理这些错误。

你可以选择性地在 except 块之后包含一个 else 块。else 块中的代码仅在 try 块完成且没有抛出任何异常时执行。

这对于仅在 try 块成功时才应该运行的代码非常有用,将它与可能出错的代码分离开来。

try:
# 高风险操作
risky_operation()
except SomeError:
print("Handling SomeError...") # 处理 SomeError...
else:
# 仅在 try 块中没有发生异常时运行
print("Operation successful, proceeding...") # 操作成功,继续进行...
perform_next_step()
#!/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 块。finally 块中的代码总是执行,无论 try 块中是否发生异常,或者异常是否被 except 块处理。即使 try 或 except 块使用了 return、break 或 continue,它也会运行。

finally 子句通常用于必须在所有情况下都发生的清理操作,例如关闭文件或网络连接、释放锁等。

try:
# 操作,可能包括资源分配
f = open("important_file.txt", "w")
f.write("Some data") # 一些数据
# 这里可能发生错误: result = 10 / 0
except 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'
#!/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: 50
Error calculating area: Width and height must be positive.

你可以通过定义继承自 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}
# 无效调用 - 如果断言启用,将抛出 AssertionError
try:
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.0
Assertion Failed: Discount percentage must be between 0 and 100.