当前位置: 首页 > 知识库问答 >
问题:

来自嵌套字典的 Python 数据类

臧烨烁
2023-03-14

3.7中的标准库可以递归地将数据类转换为判决(文档中的示例):

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

我正在寻找一种方法,当有嵌套时,可以将判决转换回数据类。像C(**tmp)这样的东西只有在数据类的字段是简单类型而不是数据类时才有效。我熟悉jsonickle,但是它带有一个突出的安全警告。

编辑:

答案建议使用以下库:

  • 英安岩
  • mashumaro(我用过一段时间,效果很好,但很快就遇到了棘手的角落案例)
  • pydantic(非常好用,优秀的文档和较少的角落案例)

共有3个答案

郭逸清
2023-03-14

只需要一个五行:

def dataclass_from_dict(klass, d):
    try:
        fieldtypes = {f.name:f.type for f in dataclasses.fields(klass)}
        return klass(**{f:dataclass_from_dict(fieldtypes[f],d[f]) for f in d})
    except:
        return d # Not a dataclass field

用法示例:

from dataclasses import dataclass, asdict

@dataclass
class Point:
    x: float
    y: float

@dataclass
class Line:
    a: Point
    b: Point

line = Line(Point(1,2), Point(3,4))
assert line == dataclass_from_dict(Line, asdict(line))

完整代码,包括与json的来往,在gist:https://gist . github . com/gatopeich/1 EFD 3 E1 e 4269 E1 e 98 FAE 9983 bb 914 f 22

池恩
2023-03-14

下面是 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)

asduce简单地用一些断言调用上面的,默认情况下dict_factory=duce

如注释中所提到的那样,如何将其修改为创建具有所需类型标记的输出字典?

1.添加类型信息

我的尝试涉及创建一个自定义返回包装器,该包装器继承自dic

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. 转换回数据类

asduce使用的递归例程可以重新用于反向过程,只需进行一些相对较小的更改:

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)])

又如预期的那样。

翟宾实
2023-03-14

我是dacite的作者-该工具简化了从字典创建数据类的过程。

这个库只有一个函数from_dict-这是一个快速的用法示例:

from dataclasses import dataclass
from dacite import from_dict

@dataclass
class User:
    name: str
    age: int
    is_active: bool

data = {
    'name': 'john',
    'age': 30,
    'is_active': True,
}

user = from_dict(data_class=User, data=data)

assert user == User(name='john', age=30, is_active=True)

此外dacite支持以下功能:

  • 嵌套结构
  • (基本)类型检查
  • 可选字段(即键入。可选)
  • 工会
  • 收集
  • 值转换和转换
  • 字段名称的重新映射

...并且经过了很好的测试——100%的代码覆盖率!

要安装达西特,只需使用管道(或管道):

$ pip install dacite
 类似资料:
  • 问题内容: 在键路径的帮助下从嵌套字典中获取值,这是: 该方法的输入参数是点分隔的关键路径,从关键路径=“ app.Garden.Flowers.white Flower”需要打印’Jasmine’。到目前为止,我的代码: 问题答案: 很接近。您需要(就像您在评论中一样)以递归方式遍历主JSON对象。您可以通过存储最外面的键/值的结果,然后使用它来获取下一个键/值等来完成操作,直到您脱离路径为止。

  • 问题内容: 我是python的新手,所以请原谅任何愚蠢的错误,但是经过研究,我无法弄清楚。我正在根据日历中月份中的几天列表创建字典。我最初使用过,但发现此提交使我确信更改为我拥有的字典理解语句。然后,我给字典中的每个值分配另一个字典,该字典以星期几作为键,另一个字典作为值。这本词典是taskDic,以家务作为键,将人的名字作为值。 我的问题是,即使循环只是在第一天,循环中的最后一条语句仍每天分配同

  • 问题内容: 我在理解Python3中的嵌套字典理解时遇到了麻烦。从下面的示例中得到的结果输出的是正确的结构,没有错误,但仅包含一个内部键:值对。我还没有找到像这样的嵌套字典理解的例子。谷歌搜索“嵌套词典理解python”显示了遗留示例,非嵌套理解或使用其他方法解决的答案。我可能使用了错误的语法。 例: 此示例应返回原始字典,但内部值由修改。 outside_dict词典的结构以及结果: 问题答案:

  • 我正在努力编写一本嵌套非常多的词典。只有当字典中有“name”:“bingo”时,我才需要获取字典的“main_id”。 我有解决办法,但在我看来是相当丑陋的。 我想知道: 有更好更干净的方法来实现它(总是;)

  • 在给定嵌套字典的情况下,如何构建二叉树?理想情况下,我希望访问根,然后以规则的深度优先或广度优先方式遍历树。 在从嵌套字典构建时间或空间方面的树时,我并不非常关心效率,所以我不介意在这个过程中使用额外的数据结构。我的主要关注点是一个全面而直观的解决方案。我现在不知道从哪里开始,所以非常感谢任何帮助。 这是二叉树的样子:

  • 问题内容: 我正在尝试将嵌套的字典写入.csv文件。这是一个简单的示例: 这使我得到一个包含两列的表:第一个包含; 第二个包含[2,1,1](或子词典中的相应值)。我想要一个有四列的表:一列对应的列表元素,然后三列对应的列表元素。 问题答案: 更改: 至: 否则,您会尝试向csv编写类似的内容,而您的意思是。 如Padraic所述,您可能希望更改为或避免随机行为。