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

如何用cattrs/attrs处理属性名与API返回键不匹配的对象构建

解决方案:通过cattrs自定义转换器实现字段映射与缺失处理

针对API返回字段与类属性名不匹配、字段可能缺失的场景,完全可以通过cattrs的自定义转换钩子实现批量处理,无需手动编写每个类的转换逻辑。以下是具体实现步骤:

1. 调整类定义,兼容字段缺失

首先给类的可选字段设置默认值或标记为Optional,避免因API返回字段缺失导致结构转换失败:

from attrs import define, field
from typing import Optional
import cattrs

@define
class PartialUser:
    id: Optional[int] = field(default=None)
    login: Optional[str] = field(default=None)

@define
class Info:
    language: Optional[str] = field(default=None)
    title: Optional[str] = field(default=None)
    delay: Optional[int] = field(default=None)
    broadcaster: Optional[PartialUser] = field(default=None)
    subscriber: Optional[PartialUser] = field(default=None)
    moderator: Optional[PartialUser] = field(default=None)

2. 编写通用转换逻辑,复用字段映射规则

实现一个通用函数,从API返回的带前缀字段(如broadcaster_id)中提取数据,构造PartialUser对象:

def structure_partial_user_from_prefix(data: dict, prefix: str) -> PartialUser:
    # 提取对应前缀的字段,处理字符串转int及空值
    user_data = {
        "id": int(data.get(f"{prefix}_id")) if data.get(f"{prefix}_id") is not None else None,
        "login": data.get(f"{prefix}_login")
    }
    return cattrs.structure(user_data, PartialUser)

3. 注册类专属转换钩子,批量处理字段映射

针对Info类注册自定义结构钩子,复用上述通用逻辑,同时处理基础字段的类型转换(如delay字符串转int):

def structure_info(data: dict, _) -> Info:
    info_data = {
        # 处理基础字段
        "language": data.get("language"),
        "title": data.get("title"),
        "delay": int(data.get("delay")) if data.get("delay") is not None else None,
        # 复用PartialUser通用转换逻辑
        "broadcaster": structure_partial_user_from_prefix(data, "broadcaster"),
        "subscriber": structure_partial_user_from_prefix(data, "subscriber"),
        "moderator": structure_partial_user_from_prefix(data, "moderator")
    }
    return Info(**info_data)

# 将钩子注册到cattrs默认转换器
cattrs.register_structure_hook(Info, structure_info)

4. 抽象逻辑适配70+类的批量处理

如果有大量类似结构的类,可以进一步抽象钩子生成函数,避免重复编写转换逻辑:

def create_prefix_based_hook(target_cls, field_prefix_map):
    """
    field_prefix_map: 键为类属性名,值为API字段前缀,例如{"broadcaster": "broadcaster"}
    """
    def hook(data: dict, _):
        instance_data = {}
        # 处理PartialUser类型的属性
        for attr_name, prefix in field_prefix_map.items():
            instance_data[attr_name] = structure_partial_user_from_prefix(data, prefix)
        # 处理基础属性,自动适配类型转换
        for field in target_cls.__attrs_attrs__:
            if field.name not in field_prefix_map:
                value = data.get(field.name)
                if field.type is int and value is not None:
                    value = int(value)
                instance_data[field.name] = value
        return target_cls(**instance_data)
    return hook

# 给Info类注册钩子,其他类可直接复用该方式
cattrs.register_structure_hook(Info, create_prefix_based_hook(Info, {
    "broadcaster": "broadcaster",
    "subscriber": "subscriber",
    "moderator": "moderator"
}))

5. 测试转换效果

执行结构转换代码,验证结果:

data = {
  "data": [
    {
      "broadcaster_id": "123",
      "broadcaster_login": "Sam",
      "language": "en",
      "subscriber_id": "1234",
      "subscriber_login": "Dave",
      "moderator_id": "12345",
      "moderator_login": "Tom",
      "delay": "0",
      "title": "Weekend Events"
    }
  ]
}

instance = cattrs.structure(data["data"][0], Info)
print(instance)
# 输出:Info(language='en', title='Weekend Events', delay=0, broadcaster=PartialUser(id=123, login='Sam'), subscriber=PartialUser(id=1234, login='Dave'), moderator=PartialUser(id=12345, login='Tom'))

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 01:25:18