在 Python 3 中,错误(Errors)和异常(Exceptions)是程序运行时出现的问题。Python 拥有一套非常优雅且强大的异常处理机制,遵循 “EAFP” 原则(Easier to Ask for Forgiveness than Permission,即“先做再道歉”,也就是先尝试执行代码,如果出错了再捕获处理),这与 C/Java 等语言的 “LBYL”(Look Before You Leap,先检查后执行)风格截然不同。
下面我将从基础语法、核心流程、自定义异常到最佳实践,为你系统梳理 Python 3 的异常处理。
一、 基础语法:try...except
这是异常处理的基石。将可能出错的代码放在 try 块中,将处理错误的逻辑放在 except 块中。
try:
# 尝试执行的代码
result = 10 / 0
except ZeroDivisionError:
# 只有当发生 ZeroDivisionError 时才会执行这里
print("❌ 错误:除数不能为零!")
except TypeError as e:
# 可以捕获多种不同类型的异常
print(f"❌ 类型错误: {e}")
else:
# 【可选】如果没有发生任何异常,则执行这里
print(f"✅ 计算结果: {result}")
finally:
# 【可选】无论是否发生异常,最后都会执行这里(常用于清理资源)
print("🔚 程序执行完毕。")输出:
❌ 错误:除数不能为零!
🔚 程序执行完毕。二、 异常处理的核心组件
1. 捕获特定异常 vs 通用异常
- 推荐:始终尽量捕获具体的异常类型(如
FileNotFoundError,KeyError)。 - 谨慎:使用裸
except:或except Exception:会捕获所有异常,这可能会掩盖真正的 Bug(如拼写错误导致的NameError)。
# ❌ 糟糕的做法:掩盖了所有潜在问题
try:
do_something()
except:
pass
# ✅ 好的做法:只捕获预期的异常
try:
value = my_dict["key"]
except KeyError:
value = "default_value"2. as 关键字:获取异常详情
使用 as e 可以将异常对象赋值给变量 e,从而打印详细的错误信息或日志。
try:
int("not_a_number")
except ValueError as e:
print(f"捕获到值错误: {e}")
# 输出: 捕获到值错误: invalid literal for int() with base 10: 'not_a_number'3. else 子句:区分正常逻辑与异常逻辑
很多新手喜欢把正常逻辑也写在 try 里,但这会导致难以定位到底是哪一行出了错。else 块确保了只有在 try 完全成功时才执行后续逻辑。
4. finally 子句:确保资源释放
无论程序是正常结束还是崩溃,finally 都会执行。它是关闭文件、数据库连接或网络套接字的最佳位置。
file = None
try:
file = open("data.txt", "r")
content = file.read()
except FileNotFoundError:
print("文件不存在")
finally:
if file:
file.close() # 确保文件被关闭(注:现代 Python 更推荐使用 with 语句自动管理资源,见下文最佳实践)
三、 主动抛出异常:raise
当你发现程序处于非法状态时,可以主动抛出异常,中断程序并通知调用者。
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}")四、 自定义异常类
在大型项目中,为了更清晰地表达业务逻辑错误,建议继承内置的 Exception 类创建自定义异常。
class InsufficientFundsError(Exception):
"""余额不足异常"""
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
super().__init__(f"余额不足! 当前余额: {balance}, 尝试取款: {amount}")
def withdraw(balance, amount):
if amount > balance:
raise InsufficientFundsError(balance, amount)
return balance - amount
try:
withdraw(100, 200)
except InsufficientFundsError as e:
print(e)
# 输出: 余额不足! 当前余额: 100, 尝试取款: 200五、 常见内置异常速查表
| 异常名称 | 触发场景 |
|---|---|
SyntaxError | 代码语法错误(解释器阶段,无法被 try-except 捕获) |
NameError | 使用了未定义的变量 |
TypeError | 对类型不支持的操作(如 "1" + 1) |
ValueError | 传入正确的类型但无效的值(如 int("abc")) |
IndexError | 列表索引越界 |
KeyError | 字典键不存在 |
FileNotFoundError | 文件路径不存在 |
AttributeError | 对象没有该属性或方法 |
ImportError / ModuleNotFoundError | 模块导入失败 |
六、 最佳实践与避坑指南
1. 优先使用 with 语句 (Context Manager)
对于文件、网络连接等资源,with 语句会自动处理 __enter__ 和 __exit__,即使发生异常也能确保资源正确关闭,比 try-finally 更简洁。
# ✅ 推荐
with open("data.txt", "r") as f:
content = f.read()
# 文件在这里自动关闭2. 不要滥用异常控制流程
异常处理的性能开销比普通 if-else 大得多。如果可以通过简单的条件判断避免错误,请优先使用 if。
# ❌ 慢:依赖异常
try:
value = my_dict["key"]
except KeyError:
value = "default"
# ✅ 快:直接检查
value = my_dict.get("key", "default")3. 异常链 (Exception Chaining)
在捕获一个异常并抛出新异常时,使用 raise ... from ... 可以保留原始异常的追踪信息,方便调试。
try:
db.connect()
except DatabaseConnectionError as e:
raise ApplicationStartupError("无法启动应用,数据库连接失败") from e4. 记录日志而不是静默忽略
永远不要写空的 except 块。至少应该记录日志,否则你的程序会在无声无息中失败,导致后期排查极其困难。
import logging
logger = logging.getLogger(__name__)
try:
process_data()
except Exception as e:
logger.error(f"数据处理失败: {e}", exc_info=True) # exc_info=True 会打印堆栈轨迹总结
Python 的异常处理旨在让代码健壮且易读。
- 小范围捕获:只捕获你预期并能处理的异常。
- 快速失败:遇到非法状态尽早
raise。 - 资源安全:多用
with,少用手动close。 - 保留现场:使用
from e和日志记录,方便排查问题。
如果你在开发中遇到了特定的报错(Traceback),可以把错误信息发给我,我帮你分析原因并提供修复方案!