1. 什么是异常?
在 Python 中,异常(Exception) 是程序运行时发生的错误或意外情况的信号。当 Python 解释器遇到无法正常执行的代码时,就会"抛出(raise)"一个异常对象。如果这个异常没有被捕获和处理,程序就会终止运行,并打印出错误信息(Traceback)。
异常与普通的语法错误不同:
- 语法错误(Syntax Error):在代码解析阶段就被发现,程序根本不会运行。
- 异常(Exception):在代码运行时发生,程序可以捕获并处理它们,让程序继续执行。
2. 常见的 Python 内置异常
Python 提供了丰富的内置异常类,它们都继承自 BaseException。以下是一些最常见的异常:
| 异常类型 | 说明 | 触发场景示例 |
|---|---|---|
ZeroDivisionError |
除以零 | 10 / 0 |
TypeError |
操作或函数应用于不适当类型的对象 | '2' + 2 |
ValueError |
传入的参数类型正确,但值不合法 | int('abc') |
IndexError |
序列索引超出范围 | list = [1, 2]; list[5] |
KeyError |
字典中查找一个不存在的键 | dict = {'a': 1}; dict['b'] |
FileNotFoundError |
尝试打开不存在的文件 | open('nonexist.txt') |
AttributeError |
尝试访问对象不存在的属性 | x = 10; x.append(2) |
ImportError |
导入模块失败 | import non_existent_module |
NameError |
尝试访问一个未定义的变量 | print(undefined_var) |
KeyboardInterrupt |
用户中断执行(如按 Ctrl+C) | 在终端运行程序时按下 Ctrl+C |
3. 异常处理的基本语法:try...except
使用 try...except 块可以捕获并处理异常,防止程序意外崩溃。
基本形式
python
try:
# 尝试执行的代码
result = 10 / 0
except ZeroDivisionError:
# 如果发生 ZeroDivisionError 异常,则执行这里的代码
print("错误:除数不能为零!")
捕获多种异常
python
try:
num = int(input("请输入一个数字: "))
result = 10 / num
print(f"结果是: {result}")
except ValueError:
print("输入错误,请输入一个有效的数字。")
except ZeroDivisionError:
print("错误:除数不能为零!")
捕获所有异常(不推荐)
使用 except Exception 可以捕获几乎所有异常,但会隐藏具体的错误信息,不利于调试。
python
try:
# 可能出错的代码
risky_operation()
except Exception as e:
print(f"发生了一个错误: {e}")
4. 完整的异常处理结构:try...except...else...finally
一个完整的异常处理块可以包含四个部分:
try:尝试执行的代码。except:捕获并处理异常。else:当没有异常发生时执行的代码。finally:无论是否发生异常,最后都会执行的代码(常用于清理资源)。
python
def divide(a, b):
try:
result = a / b
except ZeroDivisionError:
print("除数不能为零!")
return None
except TypeError:
print("参数类型错误,必须是数字。")
return None
else:
print(f"计算成功!结果是: {result}")
return result
finally:
print("除法运算执行完毕。") # 无论成功与否都会执行
# 测试
print(divide(10, 2))
print(divide(10, 0))
print(divide(10, 'a'))
5. 主动抛出异常:raise 语句
你可以使用 raise 语句主动触发异常,通常用于参数检查或定义业务逻辑错误。
python
def set_age(age):
if not isinstance(age, int):
raise TypeError("年龄必须是整数")
if age < 0 or age > 150:
raise ValueError("年龄必须在 0 到 150 之间")
print(f"年龄设置为: {age}")
try:
set_age(-5)
except ValueError as e:
print(f"输入无效: {e}")
6. 自定义异常类
通过继承 Exception 类,你可以创建自己的异常类型,使错误信息更清晰、更具业务含义。
python
class InsufficientFundsError(Exception):
"""余额不足异常"""
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
message = f"余额不足。当前余额: {balance}, 尝试提取: {amount}"
super().__init__(message)
class BankAccount:
def __init__(self, balance):
self.balance = balance
def withdraw(self, amount):
if amount > self.balance:
raise InsufficientFundsError(self.balance, amount)
self.balance -= amount
print(f"取款 {amount} 成功。剩余余额: {self.balance}")
# 使用自定义异常
account = BankAccount(100)
try:
account.withdraw(200)
except InsufficientFundsError as e:
print(e)
7. 异常链与 from 关键字
在 Python 3 中,可以使用 raise ... from ... 将底层异常与上层异常关联起来,形成清晰的异常链,便于追踪根本原因。
python
def read_config(filepath):
try:
with open(filepath, 'r') as f:
return f.read()
except FileNotFoundError as e:
# 将文件未找到的异常包装成一个更具体的配置错误
raise RuntimeError(f"无法读取配置文件: {filepath}") from e
try:
config = read_config('config.ini')
except RuntimeError as e:
print(f"程序启动失败: {e}")
print(f"根本原因: {e.__cause__}") # 这里会显示原始的 FileNotFoundError
8. 断言(assert)与异常
assert 语句用于调试,在条件为 False 时触发 AssertionError 异常。不应用于生产环境的常规数据验证 ,因为可以通过 -O 参数禁用所有断言。
python
def calculate_discount(price, discount_rate):
# 断言用于开发阶段检查假设
assert 0 <= discount_rate <= 1, "折扣率必须在 0 到 1 之间"
return price * (1 - discount_rate)
# 正常情况
print(calculate_discount(100, 0.2)) # 输出 80.0
# 触发断言异常
print(calculate_discount(100, 1.5)) # 触发 AssertionError
9. 上下文管理器与 with 语句
with 语句和上下文管理器(实现了 __enter__ 和 __exit__ 方法的对象)能优雅地处理资源的获取和释放,即使发生异常也能确保资源被清理。
python
# 文件操作是上下文管理器的经典用例
try:
with open('data.txt', 'r') as file:
content = file.read()
# 即使这里发生异常,文件也会被正确关闭
# process(content)
except FileNotFoundError:
print("文件不存在")
# 自定义一个简单的上下文管理器
class Timer:
def __enter__(self):
import time
self.start = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
import time
self.end = time.time()
print(f"代码块执行耗时: {self.end - self.start:.2f} 秒")
# 如果返回 True,则异常会被抑制,不会向上传播
return False
with Timer():
# 执行一些耗时操作
sum(i for i in range(1000000))
10. 异常处理的最佳实践
- 具体优于笼统 :尽量捕获具体的异常类型,而不是通用的
Exception。 - 不要静默所有异常:至少应该记录日志,知道错误何时何地发生。
- 保持 try 块精简 :只将可能出错的代码放入
try块。 - 合理使用 else :将正常逻辑放在
else块中,与异常处理逻辑分离。 - 善用 finally 清理资源:如关闭文件、释放网络连接等。
- 自定义异常要有意义:异常名应清晰描述问题,并包含有用的错误信息。
- 区分异常与普通错误:并非所有错误情况都适合用异常。简单的条件判断可能更合适。
总结
异常处理是编写健壮、可维护 Python 程序的核心技能。从基础的 try...except 到自定义异常和上下文管理器,理解并合理运用这些机制,能让你更好地控制程序流程,提供清晰的错误反馈,并确保资源安全。
记住:异常处理的最终目的不是掩盖错误,而是以一种可控、可预期的方式管理错误,提升程序的用户体验和稳定性。