闭包是实现数据脱敏装饰器的核心机制——它让装饰器能“记住”脱敏规则(如保留位数、替换符号),并在每次调用时复用配置,无需重复传参;其结构要求外层函数接收配置、内层函数捕获并应用参数,最终返回内层函数对象以支持@语法。

闭包是实现数据脱敏装饰器的核心机制——它让装饰器能“记住”脱敏规则(比如保留几位、用什么符号替换),并在每次函数调用时复用这些配置,而无需每次传参。
脱敏装饰器必须基于闭包的三个原因
装饰器本质是一个接收函数、返回新函数的高阶函数。要支持灵活配置(如手机号脱敏为 138****1234),就不能写死逻辑;必须通过外层函数接收配置参数,内层函数捕获并应用这些参数——这正是闭包的典型结构:
- 外层函数接收脱敏类型、保留位数等参数,形成配置环境
- 内层函数(即实际包装后的函数)引用这些参数,并在执行前后对入参或返回值做脱敏/还原处理
- 外层函数返回内层函数对象(不加括号),使装饰器可被 @ 语法调用
一个可用的手机号脱敏装饰器示例
以下代码实现:对被装饰函数的返回值中所有手机号字段自动脱敏,同时保留原始值供后续使用(如点击查看详情):
def desensitize_phone(head=3, tail=4, symbol='*'):
def decorator(func):
def wrapper(*args, **kwargs):
# 1. 先执行原函数获取结果
result = func(*args, **kwargs)
# 2. 若结果是字典,遍历 key 查找手机号格式字段
if isinstance(result, dict):
import re
for k, v in result.items():
if isinstance(v, str) and re.match(r'^1[3-9]\d{9}$', v):
masked = v[:head] + symbol * (len(v) - head - tail) + v[-tail:]
result[k + '_masked'] = masked
result[k + '_raw'] = v # 原始值保留在新 key 中
return result
return wrapper
return decorator
<h1>使用方式</h1><p>@desensitize_phone(head=3, tail=4)
def get_user_info():
return {'name': '张三', 'phone': '13812345678'}</p>调用 get_user_info() 后返回:{'name': '张三', 'phone': '13812345678', 'phone_masked': '138****5678', 'phone_raw': '13812345678'}
如何扩展支持多种敏感类型
只需在外层闭包中增加类型分发逻辑,把不同脱敏规则封装成独立函数,再由 decorator 统一调度:
- 定义脱敏策略字典,如
{'id_card': lambda s: s[:6] + '*'*8 + s[-4:], 'email': lambda s: s.split('@')[0][:2] + '**@' + s.split('@')[1]} - decorator 接收
field_type='phone'参数,在 wrapper 中根据字段名匹配策略并执行 - 关键点:策略函数本身是闭包内部可访问的,不需要全局暴露,保障封装性
注意事项与避坑点
实际落地时容易忽略几个细节:
- 避免直接修改原字典的敏感字段(如
result['phone'] = masked),可能影响上游逻辑;推荐新增带后缀的键名 - 若需对函数参数脱敏(如日志记录前抹掉密码),应在
func(*args, **kwargs)调用前处理kwargs,而非只处理返回值 - 使用
nonlocal不适用此处——脱敏配置是不可变参数,无需修改;只有计数器、缓存等需要状态更新时才需 nonlocal - 装饰器叠加时(如 @log + @desensitize),注意执行顺序:最靠近函数的装饰器最先执行(即
@desensitize包在最里层)

















