
本文介绍两种通用策略——扁平化后聚合与递归式聚合,用于对包含字典、列表、标量及 numpy 数组的嵌套树结构执行任意聚合操作(如 sum、mean、prod),并对比其语义差异与适用场景。
本文介绍两种通用策略——扁平化后聚合与递归式聚合,用于对包含字典、列表、标量及 numpy 数组的嵌套树结构执行任意聚合操作(如 sum、mean、prod),并对比其语义差异与适用场景。
在处理复杂嵌套数据(如配置树、JSON-like 结构或模型参数容器)时,常需将底层数值“向上折叠”为单一聚合结果。关键在于:聚合顺序是否影响结果? 对 sum 或 prod 这类满足结合律与交换律的函数,无论先展平再计算,还是逐层递归合并,结果一致;但对 mean、geometric_mean、list 等依赖分组结构的函数,两种策略会产生本质不同的语义。
以下提供两个核心函数,均支持任意可迭代嵌套结构(含 dict、list、tuple、np.ndarray),并正确规避字符串误展开问题:
def depth_first_search(tree):
"""深度优先遍历树,生成所有叶子节点(非容器)值"""
if isinstance(tree, dict):
for subtree in tree.values():
yield from depth_first_search(subtree)
elif isinstance(tree, (str, bytes)):
yield tree # 字符串视为原子值,不拆分为字符
else:
try:
iter(tree) # 检查是否为可迭代对象(排除 int/float/None 等)
except TypeError:
yield tree
else:
# 对 list/tuple/np.ndarray 等递归展开
for subtree in tree:
yield from depth_first_search(subtree)
def collapse_then_aggregate(f, tree):
"""先完全展平为值序列,再一次性应用聚合函数 f"""
return f(depth_first_search(tree))
def aggregate_recursively(f, tree):
"""自底向上递归聚合:对每个子树先聚合,再用 f 合并其结果"""
if isinstance(tree, dict):
subresults = (aggregate_recursively(f, v) for v in tree.values())
return f(subresults)
elif isinstance(tree, (str, bytes)):
return tree
else:
try:
iter(tree)
except TypeError:
return tree
subresults = (aggregate_recursively(f, item) for item in tree)
return f(subresults)✅ 使用示例:
from math import prod
from statistics import mean, geometric_mean
import numpy as np
tree = {
"a": [1, [2, 3, 4], [5, 6, 7, 8]],
"b": [{"c": 8, "d": 9}, {"e": 3, "f": 4}, 8]
}
# 对比结果(注意 mean/geometric_mean 的差异)
print("sum → same:", collapse_then_aggregate(sum, tree), aggregate_recursively(sum, tree))
# sum → same: 68 68
print("mean → different:",
round(collapse_then_aggregate(mean, tree), 4),
round(aggregate_recursively(mean, tree), 4))
# mean → different: 5.2308 5.0833⚠️ 注意事项:
立即学习“Python免费学习笔记(深入)”;
- collapse_then_aggregate 适用于全局统计量(如总和、乘积、中位数),语义清晰且高效;
- aggregate_recursively 保留了原始嵌套的分组逻辑,适合需按层级加权(如 mean([mean([1,2]), mean([3,4])]) ≠ mean([1,2,3,4]))或构造新结构(如 list 返回分层列表)的场景;
- 若数据含 np.ndarray,需确保 f 支持其作为输入(如 np.sum 替代内置 sum);
- 对不可迭代的标量(int, float, bool, None),两函数均直接返回原值,无需额外处理。
总结:选择策略取决于业务语义——若聚合应无视结构、仅关注所有叶子值,则用 collapse_then_aggregate;若层级关系承载语义(如“每个子模块取平均,再求模块间平均”),则必须使用 aggregate_recursively。


















