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

为含属性装饰器的类改造类装饰器,实现XML自动解析

问题

我有多个需要从解析后的XML字典构建的数据类(dataclass),但XML中的键是混淆过的,得查大量半懂的外文文档来对应结构。目前的代码如下:

from dataclasses import dataclass

def value_or_empty(xml: dict, key: str) -> str:
    return xml.get(key, "")

@dataclass
class Record:
    point_id: str
    generator_id: str
    generator_name: str
    control: str
    success: bool
    amount: int
    notes: str

    @classmethod
    def from_xml(cls, xml: dict) -> 'Record':
        return cls(
            xml["JP06400"],
            value_or_empty(xml, "JP06119"),
            value_or_empty(xml, "JP06120"),
            xml["JP06121"],
            xml["JP06122"] == "0",
            int(xml["JP06123"]),
            value_or_empty(xml, "JP06124"),
        )

显然每个类都写这类代码太繁琐,我想通过装饰器系统自动处理,已经写了如下类装饰器:

def parse_xml(cls):
    class Wrapper:
        def __init__(self, *args, **kwargs):
            self._inst = cls(*args, **kwargs)
            self.commands = []

        def __getattribute__(self, attrib):
            try:
                obj = super().__getattribute__(attrib)
                return obj
            except AttributeError:
                pass
            return self._inst.__getattribute__(attrib)

        def parse_xml(self, xml):
            for command in self.commands:
                command(self._inst, xml)

    return Wrapper

现在缺一个属性级装饰器,能给数据类的每个字段创建对应的setter,并把setter添加到类装饰器里的self.commands列表中。请问怎么实现这个能和类装饰器配合的属性级装饰器?


解决方案

1. 实现属性级装饰器xml_field

这个装饰器用来标记数据类字段对应的XML混淆键,还可以指定从XML值到字段类型的转换函数。我们把字段的元信息存储在类的专属属性里,方便后续类装饰器读取。

def xml_field(xml_key: str, converter=None, default=None):
    """标记数据类字段对应的XML键,可选转换函数和默认值"""
    def decorator(field):
        # 给字段绑定XML解析相关的元信息
        field.xml_key = xml_key
        field.converter = converter
        field.default = default
        return field
    return decorator

2. 修改类装饰器parse_xml

修改后的类装饰器会自动收集所有标记了xml_field的字段,生成对应的赋值逻辑并添加到commands列表,同时新增from_xml类方法简化实例创建流程:

def parse_xml(cls):
    # 收集当前数据类中所有标记了xml_field的字段信息
    xml_fields = []
    for field_name, field in cls.__dataclass_fields__.items():
        if hasattr(field, 'xml_key'):
            xml_fields.append((field_name, field.xml_key, field.converter, field.default))
    
    class Wrapper:
        def __init__(self, *args, **kwargs):
            self._inst = cls(*args, **kwargs)
            self.commands = []
            # 为每个标记字段生成对应的赋值函数
            for field_name, xml_key, converter, default in xml_fields:
                def make_setter(field_name, xml_key, converter, default):
                    def setter(inst, xml):
                        # 获取XML值,无对应键则用默认值
                        value = xml.get(xml_key, default)
                        # 执行转换逻辑(如果有)
                        if converter is not None and value is not None:
                            value = converter(value)
                        setattr(inst, field_name, value)
                    return setter
                self.commands.append(make_setter(field_name, xml_key, converter, default))

        def __getattribute__(self, attrib):
            try:
                obj = super().__getattribute__(attrib)
                return obj
            except AttributeError:
                pass
            return self._inst.__getattribute__(attrib)

        def parse_xml(self, xml):
            for command in self.commands:
                command(self._inst, xml)
        
        @classmethod
        def from_xml(cls, xml: dict):
            # 创建空实例并自动完成XML解析
            inst = cls()
            inst.parse_xml(xml)
            return inst._inst

    return Wrapper

3. 使用示例

现在可以用装饰器大幅简化数据类的XML解析逻辑:

from dataclasses import dataclass

def value_or_empty(xml_val):
    return xml_val if xml_val is not None else ""

@parse_xml
@dataclass
class Record:
    @xml_field("JP06400")
    point_id: str
    
    @xml_field("JP06119", converter=value_or_empty)
    generator_id: str
    
    @xml_field("JP06120", converter=value_or_empty)
    generator_name: str
    
    @xml_field("JP06121")
    control: str
    
    @xml_field("JP06122", converter=lambda x: x == "0")
    success: bool
    
    @xml_field("JP06123", converter=int)
    amount: int
    
    @xml_field("JP06124", converter=value_or_empty)
    notes: str

# 测试使用
xml_dict = {
    "JP06400": "P123",
    "JP06119": "G456",
    "JP06120": "Generator X",
    "JP06121": "ACTIVE",
    "JP06122": "0",
    "JP06123": "100",
    "JP06124": "No issues"
}

record = Record.from_xml(xml_dict)
print(record)
# 输出: Record(point_id='P123', generator_id='G456', generator_name='Generator X', control='ACTIVE', success=True, amount=100, notes='No issues')

关键说明

  • xml_field装饰器给字段绑定XML键、转换函数和默认值,这些元信息会被类装饰器自动读取。
  • 类装饰器parse_xml在初始化时生成所有字段的赋值逻辑,存入commands列表,调用parse_xml时批量执行赋值。
  • from_xml类方法整合了实例创建与XML解析流程,无需手动编写初始化逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 12:03:10