功能说明
这段代码实现配置分层合并:默认配置提供兜底值,文件配置覆盖部分字段,环境变量再覆盖最高优先级字段。与浅合并不同,嵌套字典会递归合并,因此只写 server.port 不会把整个 server 段替换掉。
它在加载 YAML、JSON 或 TOML 配置后特别有用,可以让每一层只声明自己关心的键,同时保留其他默认值。合并过程使用深拷贝,调用方可以安全复用原始字典。
关键代码
deep_merge 先用 deepcopy(dict(base)) 复制基础层,然后遍历覆盖层。若当前值和覆盖值都是 Mapping,递归合并;否则用 deepcopy(value) 直接替换。这样字典递归、标量和列表整体替换,语义清晰。
resolve_layers 按传入顺序从左到右折叠,后面的层优先级更高。main 用默认值、文件配置、环境配置三层演示,并断言结果与默认值未被污染。
使用注意
列表采用“整体替换”而不是拼接:features 会被文件层完全覆盖。如果业务需要合并列表,应显式定义去重或追加策略,不能依赖默认行为。None 会作为普通值覆盖旧值,无法表达“删除该键”;需要删除语义时要额外约定。
函数不检测循环引用,深层嵌套或自引用结构可能导致递归过深。键类型假定为字符串,混用其他类型可能让类型检查失效。配置中可能包含密钥,打印合并结果前应脱敏。
验证方式
示例断言最终 host 为 0.0.0.0、port 为 9000、features 为 ["search", "export"],同时默认配置中的 port 仍为 8000。可以再加入一层把 server 设为 None,观察结果变为 None,从而确认替换语义。
代码示例
from __future__ import annotations
from collections.abc import Mapping
from copy import deepcopy
from typing import Any
def deep_merge(
base: Mapping[str, Any],
override: Mapping[str, Any],
) -> dict[str, Any]:
merged = deepcopy(dict(base))
for key, value in override.items():
current = merged.get(key)
if isinstance(current, Mapping) and isinstance(value, Mapping):
merged[key] = deep_merge(current, value)
else:
merged[key] = deepcopy(value)
return merged
def resolve_layers(*layers: Mapping[str, Any]) -> dict[str, Any]:
result: dict[str, Any] = {}
for layer in layers:
result = deep_merge(result, layer)
return result
def main() -> None:
defaults = {
"server": {"host": "127.0.0.1", "port": 8000, "tls": False},
"features": ["search"],
}
file_config = {"server": {"port": 9000}, "features": ["search", "export"]}
env_config = {"server": {"host": "0.0.0.0"}}
resolved = resolve_layers(defaults, file_config, env_config)
print(resolved)
assert resolved["server"]["port"] == 9000
assert resolved["server"]["host"] == "0.0.0.0"
assert resolved["features"] == ["search", "export"]
assert defaults["server"]["port"] == 8000
if __name__ == "__main__":
main()