437_防御性编程技巧¶
一句话说明¶
防御性编程是"假设外部输入和调用都可能出错",提前做好断言、校验、异常处理,让代码健壮到能扛住各种意外攻击。
核心知识点¶
白话理解¶
就像过马路前先左右看——不是觉得一定有车,而是养成习惯。防御性编程也是这种思维:函数入口校验参数,调用外部服务加超时,操作文件加 try-except,让代码在任何输入下都不会"爆炸"。
核心技巧¶
- 断言(Assert):开发阶段快速暴露逻辑错误
- 输入校验:函数入口检查类型、范围、非空
- 异常处理:捕获具体异常,不用裸
except:吞掉所有错 - 防御性拷贝:函数接收可变对象时先拷贝,防止外部修改影响内部状态
- 类型注解 + 运行时校验:Python 用
typing+pydantic双保险
经典题目与解法¶
题目1:写一个健壮的除法函数,考虑所有边界¶
题意:实现 safe_divide(a, b),需要处理:非数字输入、除以零、溢出等情况。
from typing import Union
def safe_divide(a: Union[int, float], b: Union[int, float]) -> float:
"""
防御性除法:处理所有异常情况
"""
# 1. 类型校验:确保是数字类型
if not isinstance(a, (int, float)):
raise TypeError(f"参数a必须是数字,得到 {type(a).__name__}")
if not isinstance(b, (int, float)):
raise TypeError(f"参数b必须是数字,得到 {type(b).__name__}")
# 2. 非法值校验:NaN 和 Infinity 不参与计算
import math
if math.isnan(a) or math.isnan(b):
raise ValueError("参数不能是 NaN")
if math.isinf(a) or math.isinf(b):
raise ValueError("参数不能是无穷大")
# 3. 除零校验
if b == 0:
raise ZeroDivisionError("除数不能为零")
result = a / b
# 4. 结果合法性校验
if math.isinf(result):
raise OverflowError("计算结果溢出")
return result
# 测试各种边界
test_cases = [
(10, 2), # 正常
(10, 0), # 除零
("a", 2), # 类型错误
(float('nan'), 1),# NaN
(float('inf'), 2),# 无穷
]
for a, b in test_cases:
try:
print(f"{a} / {b} = {safe_divide(a, b)}")
except Exception as e:
print(f"{a} / {b} 出错: {type(e).__name__}: {e}")
时间复杂度:O(1)
空间复杂度:O(1)
题目2:实现防御性字典访问工具,避免 KeyError 和类型错误¶
题意:从嵌套字典中安全取值,支持默认值,路径不存在时不崩溃。
from typing import Any, List
def safe_get(data: dict, keys: List[str], default=None) -> Any:
"""
安全获取嵌套字典值
例:safe_get(d, ['user', 'name', 'first'], '匿名')
相当于 d['user']['name']['first'],任何层级不存在返回默认值
"""
# 断言输入类型
assert isinstance(keys, list) and len(keys) > 0, "keys必须是非空列表"
current = data # 从根节点开始
for key in keys:
if not isinstance(current, dict): # 当前节点不是字典,无法继续
return default
current = current.get(key) # 安全取值,不存在返回None
if current is None: # 取到None,后续无法继续
return default
return current # 返回找到的值
# 防御性更新:修改前深拷贝,防止污染原始数据
import copy
def safe_update(data: dict, key: str, value: Any) -> dict:
"""
返回更新后的新字典,不修改原始数据(防御性拷贝)
"""
new_data = copy.deepcopy(data) # 深拷贝,与原始数据完全隔离
new_data[key] = value
return new_data
# 测试
user = {
'name': {'first': '张', 'last': '三'},
'age': 25
}
print(safe_get(user, ['name', 'first'])) # 张
print(safe_get(user, ['address', 'city'], '未知')) # 未知(路径不存在)
print(safe_get(user, ['age', 'invalid'], 0)) # 0(age不是字典)
# 防御性更新
updated = safe_update(user, 'age', 26)
print(user['age']) # 25,原始数据未被修改
print(updated['age']) # 26
时间复杂度:O(k) k是键的层数
空间复杂度:O(n) n是数据大小(深拷贝)
面试技巧¶
- 区分断言和异常:
assert用于开发时检查不变量(生产环境可禁用),raise用于运行时错误处理 - 异常要具体:捕获
except ValueError而不是裸except:,否则连KeyboardInterrupt都被吞掉 - fail fast 原则:越早发现错误越好,函数入口校验优于深层崩溃
- 日志要充分:捕获异常后记录上下文信息,生产问题才能快速定位
- 不要静默失败:发现错误要么抛出要么记录,不能"假装没事"
速查表¶
| 技巧 | Python 写法 | 说明 |
|---|---|---|
| 类型校验 | isinstance(x, int) | 检查类型 |
| 断言 | assert cond, "msg" | 开发期快速检查 |
| 安全取值 | d.get(key, default) | 避免 KeyError |
| 异常捕获 | except (ValueError, TypeError) as e: | 具体异常 |
| 深拷贝 | copy.deepcopy(obj) | 防御性拷贝 |
| 类型注解 | def f(x: int) -> str: | 静态分析辅助 |
# 防御性编程模板
def robust_function(param):
# 1. 类型检查
if not isinstance(param, expected_type):
raise TypeError(f"期望 {expected_type},得到 {type(param)}")
# 2. 范围检查
if param < 0 or param > MAX_VALUE:
raise ValueError(f"param 超出范围 [0, {MAX_VALUE}]")
# 3. 业务逻辑
try:
result = do_something(param)
except SpecificException as e:
logger.error(f"处理失败: {e}, param={param}")
raise
return result