191 lines
5.4 KiB
Markdown
191 lines
5.4 KiB
Markdown
# 技术文档:`dictExtend` 与 `arrayExtend` 函数
|
||
|
||
## 概述
|
||
|
||
该文档描述了两个用于递归合并字典和列表的 Python 函数:
|
||
|
||
- `dictExtend(s, addon)`:递归地将一个字典 `addon` 合并到基础字典 `s` 中,支持嵌套结构。
|
||
- `arrayExtend(s, addon)`:按索引扩展列表,支持类型检查和嵌套结构的递归合并。
|
||
|
||
这两个函数常用于配置管理、默认值覆盖、数据补丁等场景,允许智能合并复杂嵌套结构的数据。
|
||
|
||
---
|
||
|
||
## 函数说明
|
||
|
||
### 1. `dictExtend(s, addon)`
|
||
|
||
#### 功能
|
||
递归合并两个字典。如果键存在于原字典中:
|
||
- 若值类型不同,则用新值覆盖;
|
||
- 若均为字典,则递归合并;
|
||
- 若均为列表,则调用 `arrayExtend` 进行合并;
|
||
- 其他情况直接覆盖。
|
||
|
||
#### 参数
|
||
| 参数 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `s` | `dict` | 原始字典(被扩展的对象) |
|
||
| `addon` | `dict` | 要合并进来的字典 |
|
||
|
||
#### 返回值
|
||
- `dict`:合并后的字典,不修改原始输入。
|
||
|
||
#### 逻辑流程
|
||
1. 创建一个新的字典 `ret`,初始化为 `s` 的副本。
|
||
2. 遍历 `addon` 的所有键值对 `(k, v)`:
|
||
- 如果 `k` 不在 `ret` 中 → 添加新键值。
|
||
- 如果 `v` 与 `ret[k]` 类型不同 → 覆盖旧值。
|
||
- 如果两者都是字典 → 递归调用 `dictExtend(ret[k], v)`。
|
||
- 如果两者都是列表 → 调用 `arrayExtend(ret[k], v)`。
|
||
- 其他情况 → 直接赋值覆盖。
|
||
3. 返回合并后的字典。
|
||
|
||
#### 示例
|
||
```python
|
||
base = {
|
||
"a": 1,
|
||
"b": {"x": 10, "y": [1, 2]},
|
||
"c": [1, 2]
|
||
}
|
||
|
||
patch = {
|
||
"b": {"y": [3], "z": 100},
|
||
"c": [3],
|
||
"d": "new"
|
||
}
|
||
|
||
result = dictExtend(base, patch)
|
||
# 结果:
|
||
# {
|
||
# "a": 1,
|
||
# "b": {"x": 10, "y": [3], "z": 100},
|
||
# "c": [3],
|
||
# "d": "new"
|
||
# }
|
||
```
|
||
|
||
---
|
||
|
||
### 2. `arrayExtend(s, addon)`
|
||
|
||
#### 功能
|
||
按索引合并两个列表。对于每个位置:
|
||
- 如果索引超出原列表长度 → 使用 `addon` 的值;
|
||
- 如果类型不同 → 使用 `addon` 的值;
|
||
- 如果都是字典 → 递归调用 `dictExtend`;
|
||
- 否则使用 `addon` 的值。
|
||
|
||
> ⚠️ 注意:当前实现存在潜在 Bug —— 最后一行 `ret += s[i:]` 实际上应为 `ret += addon[i+1:]` 或类似逻辑,目前代码可能错误拼接原始列表尾部。
|
||
|
||
#### 参数
|
||
| 参数 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `s` | `list` | 原始列表 |
|
||
| `addon` | `list` | 要合并的新列表 |
|
||
|
||
#### 返回值
|
||
- `list`:合并后的新列表,不修改原始输入。
|
||
|
||
#### 逻辑流程
|
||
1. 初始化空列表 `ret`。
|
||
2. 获取两个列表长度:`s_cnt = len(s)`, `a_cnt = len(addon)`。
|
||
3. 遍历 `addon` 的每个元素 `(i, v)`:
|
||
- 若 `i < s_cnt`(未越界):
|
||
- 类型不同 → 添加 `v`
|
||
- 是字典且对应项也是字典 → 递归合并
|
||
- 其他 → 添加 `v`
|
||
- 否则(即 `i >= s_cnt`)→ 自动添加 `v`
|
||
4. **问题点**:末尾有 `if s_cnt < a_cnt: ret += s[i:]`
|
||
此处 `i` 是循环最后的索引,`s[i:]` 表示从 `s` 的某个位置截取,这不符合“扩展”语义,**应该是 `addon[i+1:]` 才对**。
|
||
|
||
#### 示例(修正前)
|
||
```python
|
||
a = [1, {"x": 1}, [1]]
|
||
b = [2, {"x": 2, "y": 3}, [2, 3]]
|
||
|
||
result = arrayExtend(a, b)
|
||
# 当前行为(含 bug)可能导致意外结果
|
||
```
|
||
|
||
#### 存在的问题(Bug 分析)
|
||
|
||
```python
|
||
if s_cnt < a_cnt:
|
||
ret += s[i:]
|
||
```
|
||
|
||
- `i` 是 `addon` 的最后一个索引(例如 `len(addon)-1`)。
|
||
- `s[i:]` 是从原始列表 `s` 中取出从 `i` 开始的部分。
|
||
- 但此时我们希望的是把 `addon` 中多出来的部分追加,而不是 `s` 的尾部!
|
||
|
||
✅ 正确逻辑应为:
|
||
```python
|
||
ret += addon[s_cnt:] # 将 addon 多出的部分追加
|
||
```
|
||
|
||
所以此函数**存在严重逻辑错误**,需修复。
|
||
|
||
---
|
||
|
||
## 使用建议
|
||
|
||
### ✅ 正确使用场景
|
||
适用于需要深度合并嵌套配置对象的情况,如:
|
||
|
||
- 应用默认配置 + 用户自定义配置
|
||
- API 响应模板补丁
|
||
- 构建可继承的数据结构
|
||
|
||
### ❌ 已知限制与风险
|
||
1. `arrayExtend` 函数末尾逻辑错误,可能导致数据错乱。
|
||
2. 对非同构数组(mixed types)处理较粗暴,一律覆盖。
|
||
3. 无循环引用检测,深层递归可能导致栈溢出。
|
||
4. 性能一般,不适合高频或大数据量操作。
|
||
|
||
---
|
||
|
||
## 修复建议
|
||
|
||
### 修复 `arrayExtend` 函数
|
||
```python
|
||
def arrayExtend(s, addon):
|
||
ret = []
|
||
s_cnt = len(s)
|
||
a_cnt = len(addon)
|
||
|
||
for i, v in enumerate(addon):
|
||
if i < s_cnt:
|
||
if type(v) != type(s[i]):
|
||
ret.append(v)
|
||
continue
|
||
if isinstance(v, dict):
|
||
x = dictExtend(v, s[i])
|
||
ret.append(x)
|
||
continue
|
||
ret.append(v)
|
||
|
||
# 修复:应添加 addon 多出的部分,而非 s 的尾部
|
||
if a_cnt > s_cnt:
|
||
ret.extend(addon[s_cnt:])
|
||
|
||
return ret
|
||
```
|
||
|
||
---
|
||
|
||
## 总结
|
||
|
||
| 特性 | 状态 |
|
||
|------|------|
|
||
| 字典递归合并 | ✅ 支持良好 |
|
||
| 列表递归合并 | ⚠️ 支持但有 Bug |
|
||
| 类型安全检查 | ✅ 支持 |
|
||
| 不可变性 | ✅ 不修改原对象 |
|
||
| 边界情况处理 | ⚠️ 需加强(如 None、非容器类型) |
|
||
|
||
建议在实际项目中使用前进行充分测试,并优先考虑使用更成熟的库如 [`deepmerge`](https://pypi.org/project/deepmerge/) 或 `copy.deepcopy` + 手动逻辑。
|
||
|
||
---
|
||
|
||
> 📌 提示:本文档基于所提供代码分析,实际部署请先修复 `arrayExtend` 的逻辑错误。 |