Dotty 模块设计决策
目录
1. 设计目标
1.1 核心目标
Dotty 模块旨在提供:
- 简洁性:通过点号访问替代多层索引
- 便捷性:支持赋值、删除、切片等操作
- 兼容性:与原生字典 API 保持一致
- 可组合性:支持链式调用和转换
1.2 设计原则
- 不复制数据:Dotty 是原字典的代理,而非拷贝
- 延迟计算:使用 LRU 缓存优化重复访问
- 容错设计:提供安全的默认值访问
2. 核心决策
2.1 代理 vs 拷贝
决策:Dotty 使用原字典的引用,而非拷贝
原因:
- 内存效率高
- 修改直接反映到原字典
- 支持双向同步(修改 Dotty 或原字典都生效)
权衡:
- 可能导致意外修改(已通过文档说明)
2.2 工厂函数模式
决策:使用 dotty() 工厂函数而非直接实例化
python
d = dotty({'a': {'b': 1}})原因:
- 简化 API,默认参数处理
- 便于将来扩展(如添加参数验证)
- 与
json.loads()等标准库模式一致
替代方案考虑:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 工厂函数 | 灵活、易扩展 | 多一次函数调用 |
| 直接实例化 | 简单 | 扩展需要修改类 |
2.3 分隔符默认值
决策:默认使用 . 作为分隔符
原因:
- 点号是最直观的路径表示
- 与 JavaScript、Java 等语言一致
- JSONPath 等标准也使用点号
权衡:
- 键名包含点号时需要转义(已提供
esc_char)
3. API 设计
3.1 双访问模式
决策:同时支持 d['a.b.c'] 和 d.a.b.c
点号访问:
python
d['user.profile.name']属性访问:
python
d.user.profile.name原因:
- 点号访问支持嵌套列表和特殊键
- 属性访问更符合 Python 习惯
- 两者互补,满足不同场景
3.2 自动类型转换
决策:字符串数字键自动匹配整数键
python
data = {0: 'zero', 1: 'one'}
d = dotty(data)
d['0'] # 'zero' - 自动转换原因:
- 兼容不同数据源的格式差异
- 用户无需关心键类型
- 提高鲁棒性
3.3 自动创建路径
决策:赋值时自动创建不存在的路径
python
d = dotty({})
d['a.b.c'] = 1 # 自动创建 {'a': {'b': {'c': 1}}}原因:
- 简化初始化代码
- 与 JavaScript 对象访问一致
- 便于动态配置构建
3.4 列表自动扩展
决策:设置超出范围的列表索引时自动扩展
python
d = dotty({'items': []})
d['items.5'] = 'item' # 自动扩展列表原因:
- 与 JavaScript 数组行为一致
- 便于批量初始化
- 减少边界检查代码
4. 性能决策
4.1 LRU 缓存
决策:使用 @lru_cache(maxsize=32) 缓存 __getitem__
python
@lru_cache(maxsize=32)
def __getitem__(self, item):
...原因:
- 同一路径的多次访问无需重新解析
- 减少
_split()和递归调用开销 - 平衡内存和性能
缓存大小权衡:
| 大小 | 优点 | 缺点 |
|---|---|---|
| 32 | 覆盖常见场景 | 深层嵌套可能不足 |
| 更大 | 缓存命中率高 | 内存占用高 |
| 更小 | 内存占用低 | 缓存命中率低 |
4.2 缓存限制
决策:缓存绑定到方法调用,不考虑实例状态变更
影响:
python
d = dotty(data)
d['a.b.c'] # 缓存结果
d['a.b.c'] = 'new value' # 修改了原字典
d['a.b.c'] # 可能返回缓存的旧值文档说明:修改数据后避免依赖缓存结果
4.3 迭代 vs 递归
决策:使用递归实现 __getitem__
python
def get_from(items, data):
it = items.pop(0)
...
if items:
return get_from(items, data) # 递归
return data原因:
- 代码简洁,易理解
- 嵌套深度可控(Python 递归限制)
- 性能可接受
替代方案:使用循环
- 代码更复杂
- 性能差异不大
5. 兼容性决策
5.1 Python 版本兼容
决策:使用 collections.abc.Mapping 兼容 Python 3.10+
python
try:
from collections.abc import Mapping
except ImportError:
from collections import Mapping原因:
collections.Mapping在 Python 3.10+ 已废弃- 保持向后兼容
5.2 JSON 序列化
决策:提供 DottyEncoder 支持 JSON 序列化
python
json.dumps(data, cls=DottyEncoder)原因:
- 便于在序列化时展开 Dotty 对象
- 与标准
json模块集成
5.3 字典协议兼容
决策:实现多个字典协议方法
| 方法 | 协议 |
|---|---|
__getitem__ | Mapping |
__setitem__ | MutableMapping |
__contains__ | Container |
__len__ | Sized |
__eq__ | Object |
原因:
- 与原生字典 API 一致
- 便于替换使用
- IDE 支持更好
6. 未来演进方向
6.1 可能的变化
| 变化 | 触发条件 |
|---|---|
| 支持自定义键类型 | 复杂键类型需求 |
| 惰性求值 | 超深层嵌套优化 |
| 更多切片模式 | 用户场景扩展 |
6.2 不纳入的设计
- 路径通配符:如
user.*.name,增加复杂度 - 路径验证:如检查路径是否存在,增加开销
- 默认值工厂:如
default=lambda: [],增加 API 复杂度