如何让子类化list的自定义类可被dataclasses.asdict()序列化?
问题:Dataclass中自定义List子类使用
asdict()返回空列表 在Windows环境的Python 3.11.3中,尝试将dataclass作为强类型字典使用时,遇到子类化list的自定义类型在asdict()处理后返回空列表的异常行为,但使用普通list[float]则正常。
问题复现代码
from dataclasses import dataclass, asdict class CustomFloatList(list): def __init__(self, args): for i, arg in enumerate(args): assert isinstance(arg, float), f"Expected index {i} to be a float, but it's a {type(arg).__name__}" super().__init__(args) @classmethod def from_list(cls, l: list[float]): return cls(l) @dataclass class Poc: x: CustomFloatList p = Poc(x=CustomFloatList.from_list([3.0])) print(p) # 输出 Poc(x=[3.0]) print(p.x) # 输出 [3.0] print(asdict(p)) # 异常输出 {'x': []}
问题原因
问题出在CustomFloatList的__init__方法中:
- 代码先通过
enumerate(args)对传入的args进行第一次迭代(做类型校验),如果args是一次性迭代器(比如asdict()内部处理时传入的迭代器),这次迭代会消耗掉所有元素。 - 随后调用
super().__init__(args)时,args已经没有剩余元素,导致父类list构造出空列表。
而使用普通list时,asdict()会直接处理其内部存储的元素,不会触发这种迭代器被多次消耗的问题。
解决方案
方案1:提前将args转为可重复迭代的列表(你的修改方案)
在类型校验前,先把args转为列表,确保后续两次使用的是同一个可重复访问的对象:
from dataclasses import dataclass, asdict class CustomFloatList(list): def __init__(self, args): dup_args = list(args) # 转为列表,避免迭代器被消耗 for i, arg in enumerate(dup_args): assert isinstance(arg, float), f"Expected index {i} to be a float, but it's a {type(arg).__name__}" super().__init__(dup_args) @classmethod def from_list(cls, l: list[float]): return cls(l) @dataclass class Poc: x: CustomFloatList p = Poc(x=CustomFloatList.from_list([3.0])) print(p) print(p.x) print(asdict(p)) # 正常输出 {'x': [3.0]}
方案2:优化迭代逻辑(更高效)
如果想避免额外的列表拷贝,可以在一次迭代中同时完成校验和收集元素:
from dataclasses import dataclass, asdict class CustomFloatList(list): def __init__(self, args): validated = [] for i, arg in enumerate(args): assert isinstance(arg, float), f"Expected index {i} to be a float, but it's a {type(arg).__name__}" validated.append(arg) super().__init__(validated) @classmethod def from_list(cls, l: list[float]): return cls(l) @dataclass class Poc: x: CustomFloatList p = Poc(x=CustomFloatList.from_list([3.0])) print(asdict(p)) # 正常输出 {'x': [3.0]}
方案3:使用__post_init__做校验(更符合dataclass风格)
把校验逻辑放到__post_init__中,避免干扰父类的初始化流程:
from dataclasses import dataclass, asdict class CustomFloatList(list): def __post_init__(self): for i, arg in enumerate(self): assert isinstance(arg, float), f"Expected index {i} to be a float, but it's a {type(arg).__name__}" @classmethod def from_list(cls, l: list[float]): return cls(l) @dataclass class Poc: x: CustomFloatList p = Poc(x=CustomFloatList.from_list([3.0])) print(asdict(p)) # 正常输出 {'x': [3.0]}
补充说明
- 不建议直接使用
.__dict__,asdict()会递归处理嵌套的dataclass、集合等类型,是官方推荐的dataclass转字典方式。 - 自定义序列类型时,要注意处理迭代器的一次性特性,确保多次访问数据源时能获取到完整元素。
内容的提问来源于stack exchange,提问作者ijustlovemath
相关产品推荐
相关产品推荐

