Python 异常处理
目录
学习目标
- 理解异常的概念和异常层次结构
- 掌握 try/except/else/finally 的用法
- 学会捕获和处理多种异常
- 理解异常的传播机制
1. 什么是异常
**异常(Exception)**是程序执行期间发生的错误,会中断正常的程序流程。
# 常见的异常
print(1 / 0) # ZeroDivisionError
print(int("abc")) # ValueError
print([1, 2][10]) # IndexError
print({"a": 1}["b"]) # KeyError1.1 内置异常层次
BaseException
├── SystemExit # sys.exit() 引发
├── KeyboardInterrupt # Ctrl+C 引发
└── Exception # 常规异常的基类
├── ArithmeticError
│ └── ZeroDivisionError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── TypeError
├── ValueError
├── AttributeError
├── NameError
│ └── UnboundLocalError
├── RuntimeError
├── OSError
│ ├── FileNotFoundError
│ └── PermissionError
└── ...2. try/except 基础
2.1 基本结构
try:
result = 10 / 0
except ZeroDivisionError:
print("不能除以零!")
print("程序继续执行")2.2 捕获多种异常
def divide(a, b):
try:
result = a / b
return result
except ZeroDivisionError:
print("错误:除数不能为零")
return None
except TypeError:
print("错误:参数必须是数字")
return None
# 测试
print(divide(10, 2)) # 5.0
print(divide(10, 0)) # 错误:除数不能为零
print(divide(10, "a")) # 错误:参数必须是数字2.3 捕获异常对象
try:
num = int("abc")
except ValueError as e:
print(f"捕获到异常: {e}")
print(f"异常类型: {type(e).__name__}")2.4 元组捕获多种异常
try:
# 可能引发 ValueError 或 TypeError
value = int(input("请输入数字: "))
result = 100 / value
except (ValueError, TypeError) as e:
print(f"输入错误: {e}")
except ZeroDivisionError:
print("不能输入零")3. else 和 finally
3.1 else 子句
else 在 没有异常时 执行。
try:
result = 10 / 2
except ZeroDivisionError:
print("除以零错误")
else:
print(f"计算成功: {result}") # 这行会执行3.2 finally 子句
finally 无论是否发生异常 都会执行,常用于清理资源。
def read_file(filename):
file = None
try:
file = open(filename, 'r')
return file.read()
except FileNotFoundError:
print(f"文件不存在: {filename}")
return ""
finally:
print("清理资源...")
if file:
file.close()
print("文件已关闭")
# 测试
content = read_file('不存在的文件.txt')3.3 完整结构
try:
print("尝试执行...")
result = 10 / 2
except ZeroDivisionError:
print("捕获异常")
else:
print(f"成功: {result}")
finally:
print("总是执行")
# 输出:
# 尝试执行...
# 成功: 5.0
# 总是执行4. 异常传播
4.1 异常向上传播
def func_c():
print("func_c 开始")
raise ValueError("C 出错了")
def func_b():
print("func_b 开始")
func_c()
print("func_b 结束") # 不会执行
def func_a():
print("func_a 开始")
try:
func_b()
except ValueError as e:
print(f"func_a 捕获: {e}")
print("func_a 结束")
func_a()输出:
func_a 开始
func_b 开始
func_c 开始
func_a 捕获: C 出错了
func_a 结束4.2 重新抛出异常
def process_data(data):
try:
result = int(data) / 10
except ValueError:
print("数据格式错误,记录日志...")
raise # 重新抛出原始异常
return result
try:
process_data("abc")
except ValueError as e:
print(f"最终捕获: {e}")4.3 抛出新异常
def divide_safe(a, b):
try:
return a / b
except ZeroDivisionError:
raise ValueError(f"除数不能为零,收到: {b}")
try:
divide_safe(10, 0)
except ValueError as e:
print(f"错误: {e}")4.4 保留异常链
def convert_and_divide(data, divisor):
try:
number = int(data)
except ValueError as e:
raise ValueError(f"无法转换: {data}") from e
try:
return number / divisor
except ZeroDivisionError as e:
raise ValueError(f"除数不能为零") from e
try:
convert_and_divide("abc", 0)
except ValueError as e:
print(f"错误: {e}")
if e.__cause__:
print(f"原因: {e.__cause__}")5. 主动抛出异常
5.1 raise 语句
def set_age(age):
if age < 0:
raise ValueError("年龄不能为负数")
if age > 150:
raise ValueError("年龄不合理")
return age
try:
set_age(-5)
except ValueError as e:
print(f"错误: {e}")5.2 断言 assert
def divide(a, b):
assert b != 0, "除数不能为零" # 条件为 False 时抛出 AssertionError
return a / b
# 生产环境通常关闭断言: python -O script.py
# divide(10, 0) # AssertionError6. 最佳实践
6.1 具体捕获,不要裸 except
# 不推荐 - 捕获所有异常(包括 SystemExit、KeyboardInterrupt)
try:
do_something()
except: # 捕获 BaseException
pass
# 推荐 - 捕获具体异常
try:
do_something()
except ValueError as e:
print(f"值错误: {e}")
except Exception as e:
print(f"其他错误: {e}")6.2 不要吞掉异常
# 不推荐 - 静默忽略异常
try:
risky_operation()
except Exception:
pass # 错误被隐藏了!
# 推荐 - 至少记录日志
try:
risky_operation()
except Exception as e:
print(f"操作失败: {e}")
# 或: logging.error(f"操作失败: {e}")6.3 使用上下文管理器处理资源
# 推荐 - 使用 with 语句
with open('file.txt', 'r') as f:
content = f.read()
# 而不是
try:
f = open('file.txt', 'r')
content = f.read()
finally:
f.close()本节小结
- 异常:程序执行中的错误,使用
try/except捕获处理 - 结构:
try->except->else(无异常时)->finally(总是执行) - 传播:未捕获的异常会向上传播,直到被处理或程序终止
- 重新抛出:使用裸
raise或raise ... from保留异常链 - 最佳实践:捕获具体异常、不要静默吞掉、善用
with语句
练习
- 编写一个函数,安全地将字符串转换为整数,失败时返回默认值
- 创建一个文件读取函数,处理所有可能的文件异常
- 实现一个嵌套调用链,在最外层统一捕获并记录异常
- 编写一个装饰器,自动捕获函数异常并打印友好错误信息