You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用python-attrs将嵌套字典转为可属性访问的嵌套对象列表?

用python-attrs实现嵌套字典的"Objectify"方案

我之前刚好折腾过类似的需求,用python-attrs完全能搞定,而且你提到的循环调用attr.make_class()不仅可行,还是实现这个嵌套转换的核心思路,完美契合你所有的约束条件(保留顺序、支持嵌套、属性访问)。

核心思路拆解

我们需要一个递归处理逻辑,对不同类型的数据做针对性转换:

  • 基础类型/None:直接返回,不需要转换
  • 列表/元组:遍历每个元素递归转换,保留原序列类型
  • 字典:用attr.make_class()动态生成对应的attrs类,按原字典的键顺序定义类属性,同时递归处理每个值,最后实例化这个动态类

完整代码实现

首先导入必要的模块:

import attr
from typing import Any

然后写递归转换函数:

def objectify(data: Any) -> Any:
    # 处理字符串、数字、None这类基础类型
    if isinstance(data, (str, int, float, type(None))):
        return data
    
    # 处理列表和元组,递归转换每个元素后保留原类型
    elif isinstance(data, (list, tuple)):
        converted_items = [objectify(item) for item in data]
        return tuple(converted_items) if isinstance(data, tuple) else converted_items
    
    # 处理字典,动态生成attrs类
    elif isinstance(data, dict):
        # 按原字典的键顺序构建类字段,确保顺序保留
        class_fields = {}
        for key, value in data.items():
            # 递归处理每个值,作为字段的默认值
            converted_val = objectify(value)
            class_fields[key] = attr.ib(default=converted_val)
        
        # 动态生成类:用字典的id做后缀避免类名冲突,order=False关闭自动比较方法
        dynamic_class = attr.make_class(
            f"DynamicObj_{id(data)}",
            class_fields,
            order=False,
            kw_only=False
        )
        
        # 实例化并返回
        return dynamic_class(**data)
    
    # 其他未预期类型直接返回
    else:
        return data

测试示例输入

用你给出的样例测试一下:

sample_input = {
    'some_key': [
        {'some_value': 12, 'foo': 'abc', 'bar': [1, 4, 8], 'baz': None},
        {'some_value': 24, 'foo': 'xyz', 'bar': [12, 12], 'baz': 'this'}
    ],
    'other_key': [1,2,3,4]
}

# 转换数据
result = objectify(sample_input)

# 验证属性访问
print(result.some_key[0].some_value)  # 输出 12
print(result.some_key[0].baz)        # 输出 None
print(result.other_key[-1])          # 输出 4

关键细节说明

  1. 顺序保留:Python 3.7+的字典本身是插入有序的,我们遍历字典键值对的顺序就是原数据的顺序,生成attrs类时字段顺序和原字典一致,所以实例的属性顺序完全保留。
  2. 动态类的可行性:循环/递归调用attr.make_class()完全没问题,这个API就是为动态生成attrs类设计的。每个不同结构的字典会生成独立的动态类,用id(data)做类名后缀是为了避免不同字典(哪怕结构相同)生成同名类的冲突。
  3. 嵌套处理:递归函数会穿透所有层级的嵌套字典和序列,确保每个嵌套结构都被转换成可属性访问的对象。

可选优化:复用相同结构的类

如果你的数据中有大量结构相同的字典,可以加个缓存避免重复生成相同的动态类,提升性能:

from functools import lru_cache

# 生成字典结构的哈希键,用于缓存
def _get_struct_key(data: dict) -> tuple:
    struct_items = []
    for key, value in data.items():
        if isinstance(value, dict):
            struct_items.append((key, _get_struct_key(value)))
        elif isinstance(value, (list, tuple)):
            elem_key = _get_struct_key(value[0]) if value else None
            struct_items.append((key, (type(value).__name__, elem_key)))
        else:
            struct_items.append((key, type(value).__name__))
    return tuple(struct_items)

# 缓存已生成的动态类
_class_cache = {}

def objectify(data: Any) -> Any:
    if isinstance(data, (str, int, float, type(None))):
        return data
    elif isinstance(data, (list, tuple)):
        converted_items = [objectify(item) for item in data]
        return tuple(converted_items) if isinstance(data, tuple) else converted_items
    elif isinstance(data, dict):
        struct_key = _get_struct_key(data)
        # 先查缓存,没有再生成
        if struct_key not in _class_cache:
            class_fields = {}
            for key, value in data.items():
                converted_val = objectify(value)
                class_fields[key] = attr.ib(default=converted_val)
            _class_cache[struct_key] = attr.make_class(
                f"DynamicObj_{hash(struct_key)}",
                class_fields,
                order=False,
                kw_only=False
            )
        return _class_cache[struct_key](**data)
    else:
        return data

关于循环调用attr.make_class()的疑问

完全可行!这是处理这种动态嵌套场景的非常自然的方式。attr.make_class()本身就支持在运行时动态创建attrs类,递归/循环调用它可以轻松处理任意深度的嵌套字典结构,只要我们按正确的逻辑处理每个层级的字段和值就行。

内容的提问来源于stack exchange,提问作者Philippe Ombredanne

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:55:14