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] [1],但是它带有突出的安全警告。

编辑:
答案建议使用以下库:
  • dacite
  • mashumaro(我使用了一段时间,效果很好,但很快遇到了棘手的问题)
  • pydantic(效果很好,出色的文档和较少的案例)
    [1]:https://jsonpickle.github.io/
  • 最佳答案

    下面是asdict的CPython实现
    –或具体来说,它使用的内部递归帮助函数_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



    1.添加类型信息

    我的尝试涉及创建自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的容器:
    # 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)])
    

    再次如预期。

    关于python - 嵌套字典中的Python数据类,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/53376099/

    10-12 18:43