3.7中的标准库可以将数据类递归转换为dict(来自文档的示例):
from dataclasses import dataclass, asdict from typing import List @dataclass class Point: x: int y: int @dataclass class C: mylist: List[Point] p = Point(10, 20) assert asdict(p) == {'x': 10, 'y': 20} c = C([Point(0, 0), Point(10, 4)]) tmp = {'mylist': [{'x': 0, 'y': 0}, {'x': 10, 'y': 4}]} assert asdict(c) == tmp
我正在寻找一种在嵌套时将dict转换回数据类的方法。C(**tmp)只有在数据类的字段是简单类型而不是数据类本身的情况下,类似的东西才起作用。我熟悉jsonpickle,但是它带有突出的安全警告。
C(**tmp)
下面是它使用asdict 的内部递归帮助器功能的CPython实现_asdict_inner:
asdict
_asdict_inner
# Source: https://github.com/python/cpython/blob/master/Lib/dataclasses.py def _asdict_inner(obj, dict_factory): if _is_dataclass_instance(obj): result = [] for f in fields(obj): value = _asdict_inner(getattr(obj, f.name), dict_factory) result.append((f.name, value)) return dict_factory(result) elif isinstance(obj, tuple) and hasattr(obj, '_fields'): # [large block of author comments] return type(obj)(*[_asdict_inner(v, dict_factory) for v in obj]) elif isinstance(obj, (list, tuple)): # [ditto] return type(obj)(_asdict_inner(v, dict_factory) for v in obj) elif isinstance(obj, dict): return type(obj)((_asdict_inner(k, dict_factory), _asdict_inner(v, dict_factory)) for k, v in obj.items()) else: return copy.deepcopy(obj)
asdict只需在dict_factory=dict默认情况下使用一些断言调用上述方法即可。
dict_factory=dict
如注释中所述,如何调整它以创建带有所需类型标记的输出字典?
1.添加类型信息
我的尝试涉及创建一个自定义继承自的返回包装器dict:
dict
class TypeDict(dict): def __init__(self, t, *args, **kwargs): super(TypeDict, self).__init__(*args, **kwargs) if not isinstance(t, type): raise TypeError("t must be a type") self._type = t @property def type(self): return self._type
综观原代码,只有第一条需要进行修改,以使用该包装,与其他条款只处理 集装箱 的dataclass-es:
dataclass
# only use dict for now; easy to add back later def _todict_inner(obj): if is_dataclass_instance(obj): result = [] for f in fields(obj): value = _todict_inner(getattr(obj, f.name)) result.append((f.name, value)) return TypeDict(type(obj), result) elif isinstance(obj, tuple) and hasattr(obj, '_fields'): return type(obj)(*[_todict_inner(v) for v in obj]) elif isinstance(obj, (list, tuple)): return type(obj)(_todict_inner(v) for v in obj) elif isinstance(obj, dict): return type(obj)((_todict_inner(k), _todict_inner(v)) for k, v in obj.items()) else: return copy.deepcopy(obj)
进口:
from dataclasses import dataclass, fields, is_dataclass # thanks to Patrick Haugh from typing import * # deepcopy import copy
使用的功能:
# copy of the internal function _is_dataclass_instance def is_dataclass_instance(obj): return is_dataclass(obj) and not is_dataclass(obj.type) # the adapted version of asdict def todict(obj): if not is_dataclass_instance(obj): raise TypeError("todict() should be called on dataclass instances") return _todict_inner(obj)
使用示例数据类进行测试:
c = C([Point(0, 0), Point(10, 4)]) print(c) cd = todict(c) print(cd) # {'mylist': [{'x': 0, 'y': 0}, {'x': 10, 'y': 4}]} print(cd.type) # <class '__main__.C'>
结果符合预期。
2.转换回dataclass
所使用的递归例程asdict可以用于反向过程,但需要进行一些相对较小的更改:
def _fromdict_inner(obj): # reconstruct the dataclass using the type tag if is_dataclass_dict(obj): result = {} for name, data in obj.items(): result[name] = _fromdict_inner(data) return obj.type(**result) # exactly the same as before (without the tuple clause) elif isinstance(obj, (list, tuple)): return type(obj)(_fromdict_inner(v) for v in obj) elif isinstance(obj, dict): return type(obj)((_fromdict_inner(k), _fromdict_inner(v)) for k, v in obj.items()) else: return copy.deepcopy(obj)
def is_dataclass_dict(obj): return isinstance(obj, TypeDict) def fromdict(obj): if not is_dataclass_dict(obj): raise TypeError("fromdict() should be called on TypeDict instances") return _fromdict_inner(obj)
测试:
c = C([Point(0, 0), Point(10, 4)]) cd = todict(c) cf = fromdict(cd) print(c) # C(mylist=[Point(x=0, y=0), Point(x=10, y=4)]) print(cf) # C(mylist=[Point(x=0, y=0), Point(x=10, y=4)])
再次如预期。