Skip to content

Dotty 模块设计决策

目录

  1. 设计目标
  2. 核心决策
  3. API 设计
  4. 性能决策
  5. 兼容性决策

1. 设计目标

1.1 核心目标

Dotty 模块旨在提供:

  • 简洁性:通过点号访问替代多层索引
  • 便捷性:支持赋值、删除、切片等操作
  • 兼容性:与原生字典 API 保持一致
  • 可组合性:支持链式调用和转换

1.2 设计原则

  1. 不复制数据:Dotty 是原字典的代理,而非拷贝
  2. 延迟计算:使用 LRU 缓存优化重复访问
  3. 容错设计:提供安全的默认值访问

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 复杂度