为含属性装饰器的类改造类装饰器,实现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
相关产品推荐
相关产品推荐

