一、引言:什么是异常?
在程序运行过程中,可能会遇到各种意外情况,例如:
- ❌ 用户输入了无效的数据
- ❌ 尝试打开一个不存在的文件
- ❌ 除以零
- ❌ 网络连接中断
- ❌ 访问列表越界
这些情况会导致程序中断执行并抛出异常(Exception)。如果不加以处理,程序会崩溃并显示错误信息。
✅ 异常处理的目标:让程序在出错时能够优雅降级,而不是直接崩溃,提升程序的健壮性和用户体验。
Python 提供了强大的异常处理机制,允许我们捕获和处理错误,确保程序的稳定运行。
二、常见的内置异常类型
异常类型 |
描述 |
示例 |
|
所有内置异常的基类 |
通用捕获 |
|
值不合适(类型正确但值非法) |
|
|
类型错误 |
|
|
序列索引超出范围 |
|
|
字典中不存在指定的键 |
|
|
文件未找到 |
|
|
除以零 |
|
|
输入/输出操作失败 |
读写文件失败 |
|
模块导入失败 |
|
|
对象没有该属性 |
|
💡 查看所有内置异常:
help('built-in')
三、基本语法:try-except
结构
1. 最简单的异常处理(不推荐)
try:
result = 10 / 0
except:
print("发生了错误!")
⚠️ 不推荐:捕获所有异常而不指定具体类型,不利于调试和错误定位。
2. 捕获特定异常(推荐)
try:
num = int(input("请输入一个数字:"))
result = 10 / num
except ValueError:
print("输入的不是有效数字!")
except ZeroDivisionError:
print("不能除以零!")
✅ 推荐:精确捕获你预期的异常类型。
3. 捕获多个异常
try:
value = int("abc")
except (ValueError, TypeError):
print("转换失败:输入的值无法转换为整数")
✅ 使用元组 (A, B)
可以同时捕获多种异常。
4. 获取异常信息
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f"发生异常:{e}") # 输出:division by zero
✅ 使用 as e
可以获取异常实例,便于日志记录或进一步处理。
四、完整的异常处理结构:try-except-else-finally
1. else
子句
当 try
块中没有发生异常时执行。
try:
num = int(input("请输入一个数字:"))
except ValueError:
print("输入无效!")
else:
print(f"你输入的数字是:{num}")
# 只有在没有异常时才会执行
✅ else
用于放置“成功路径”的代码,避免意外捕获本不该处理的异常。
2. finally
子句
无论是否发生异常,都会执行,常用于清理资源(如关闭文件、释放锁等)。
file = None
try:
file = open("test.txt", "r")
content = file.read()
print(content)
except FileNotFoundError:
print("文件未找到")
finally:
if file:
file.close() # 确保文件被关闭
print("文件已关闭")
✅ 保证资源释放,防止资源泄漏。
✅ 最佳实践:使用 with
语句自动管理资源
# 推荐方式:自动管理文件资源
try:
with open("test.txt", "r") as file:
content = file.read()
print(content)
except FileNotFoundError:
print("文件未找到")
# 文件会自动关闭,无需 finally
✅ with
语句利用上下文管理器(Context Manager)自动调用 __enter__
和 __exit__
,更安全、简洁。
五、手动抛出异常:raise
你可以使用 raise
语句主动抛出异常,用于验证输入、业务规则等。
1. 抛出内置异常
age = -5
if age < 0:
raise ValueError("年龄不能为负数")
2. 抛出自定义异常
class InvalidAgeError(Exception):
"""自定义异常:无效年龄"""
pass
age = 150
if age > 120:
raise InvalidAgeError("年龄不能超过120岁")
六、自定义异常
通过继承 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:
new_balance = withdraw(100, 150)
except InsufficientFundsError as e:
print(e) # 输出:余额不足:当前余额 100,请求取款 150
print(f"差额:{e.amount - e.balance}") # 50
✅ 自定义异常可以携带上下文信息,便于调试和业务处理。
七、异常处理的最佳实践
1. 只捕获你知道如何处理的异常
# ❌ 错误做法:静默忽略所有错误
try:
risky_operation()
except:
pass
# ✅ 正确做法:只处理特定异常
try:
process_data()
except ValueError as e:
print(f"数据格式错误:{e}")
except FileNotFoundError:
print("文件不存在,请检查路径")
2. 提供有意义的错误信息
try:
user = users[user_id]
except KeyError as e:
print(f"用户不存在:ID {user_id} 未找到")
✅ 帮助用户或开发者快速定位问题。
3. 使用 logging
替代 print
(生产环境)
import logging
try:
connect_to_database()
except ConnectionError as e:
logging.error(f"数据库连接失败:{e}", exc_info=True)
✅ logging
支持日志级别、格式化、文件输出等,更适合生产环境。
4. 避免在 finally
中使用 return
def bad_example():
try:
raise ValueError("oops")
finally:
return "done" # ❌ 会掩盖上面的异常!
print(bad_example()) # 输出:done,但原始异常丢失!
⚠️ finally
中的 return
会覆盖try
或 except
中的返回值或异常。
八、综合示例:一个健壮的计算器
def safe_divide(a, b):
try:
result = a / b
except ZeroDivisionError:
print("错误:不能除以零!")
return None
except TypeError:
print("错误:请输入有效的数字!")
return None
else:
print(f"计算成功:{a} / {b} = {result}")
return result
finally:
print("除法操作完成")
# 测试
print(safe_divide(10, 2)) # 5.0
print(safe_divide(10, 0)) # None
print(safe_divide(10, "a")) # None
✅ 包含完整异常处理流程:捕获、处理、成功路径、清理。
九、总结:异常处理的核心要点
结构 |
作用 |
是否必需 |
|
包含可能出错的代码 |
✅ 必需 |
|
捕获并处理异常 |
⚠️ 建议使用 |
|
|
❌ 可选 |
|
无论是否异常都执行(清理) |
❌ 可选 |
|
主动抛出异常 |
❌ 按需使用 |
十、动手练习
1. 编写一个函数,读取用户输入的两个数字并计算它们的商
def input_and_divide():
try:
a = float(input("请输入被除数:"))
b = float(input("请输入除数:"))
result = a / b
except ValueError:
print("请输入有效的数字!")
except ZeroDivisionError:
print("除数不能为零!")
else:
print(f"结果是:{result}")
finally:
print("计算完成")
input_and_divide()
2. 创建自定义异常 NegativeNumberError
class NegativeNumberError(Exception):
def __init__(self, value):
self.value = value
super().__init__(f"不允许负数:{value}")
def process_positive_number(num):
if num < 0:
raise NegativeNumberError(num)
return num * 2
# 测试
try:
print(process_positive_number(5)) # 10
print(process_positive_number(-3)) # 抛出异常
except NegativeNumberError as e:
print(e)
3. 使用 try-finally
确保资源关闭(模拟数据库连接)
class DatabaseConnection:
def __init__(self, db_name):
self.db_name = db_name
self.connected = False
def connect(self):
print(f"连接到数据库 {self.db_name}...")
self.connected = True
def close(self):
if self.connected:
print(f"关闭数据库连接 {self.db_name}")
self.connected = False
# 模拟使用
conn = DatabaseConnection("mydb")
try:
conn.connect()
# 模拟操作
print("执行数据库查询...")
# raise Exception("查询失败") # 测试异常
except Exception as e:
print(f"数据库操作失败:{e}")
finally:
conn.close() # 确保连接关闭
🚀 学习建议
- 多练习:编写包含异常处理的函数,模拟各种错误场景。
- 理解异常层次结构:
BaseException → Exception → ...
,便于精确捕获。 - 设计良好的异常策略:根据业务需求决定是“失败重试”、“降级处理”还是“直接报错”。
- 进阶学习:
-
- 上下文管理器(
__enter__
,__exit__
) - 异常链(
raise ... from ...
) - 断言(
assert
)与调试
- 上下文管理器(
🎯 记住:良好的异常处理是专业代码的标志。它不仅防止程序崩溃,还能提供清晰的反馈,提升系统可靠性。
祝你写出健壮、可靠的 Python 程序!