问答中心分类: PYTHON来自嵌套dict的Python数据类
0
匿名用户 提问 15分钟 前

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)仅当数据类的字段是简单类型而不是其本身的数据类时才有效。我熟悉杰森·皮克然而,它附带了一个突出的安全警告。

编辑:
答案建议使用以下库:

  • 英安岩
  • mashumaro(我用了一段时间,效果很好,但很快就遇到了棘手的情况)
  • pydantic(效果很好,文档很好,转角案例更少)
15 Answers
0
Konrad Hałas 回答 15分钟 前

我是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支持以下功能:

  • 嵌套结构
  • (基本)类型检查
  • 可选字段(即键入。可选)
  • 工会
  • collections
  • 价值观铸造与转换
  • 字段名称的重新映射

… 它经过了充分的测试——100%的代码覆盖率!
要安装dacite,只需使用pip(或pipenv):

$ pip install dacite
Alex 回复 15分钟 前

令人惊叹的我们如何建议将此功能添加到python标准库中?:-)

Jurass 回复 15分钟 前

我不理解为什么heck-Python带来了数据类,但没有增加从包含嵌套类的字典创建它们的可能性。

Guy Mazuz 回复 15分钟 前

这是金子!:-)

rv.kvetch 回复 15分钟 前

不幸的是,它也很慢。虽然验证是一个很好的特性。

Konrad Hałas 回复 15分钟 前

@rv。kvetch-你可以试试这个分支github。com/konradhalas/dacite/tree/feature/…它有多项性能改进

0
gatopeich 回答 15分钟 前

只需要五行:

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到json的代码,见gist:https://gist.github.com/gatopeich/1efd3e1e4269e1e98fae9983bb914f22

igoras1993 回复 15分钟 前

这应该是公认的答案。五行代码,没有外部依赖关系+1.

igoras1993 回复 15分钟 前

好的,经过了深入的测试,现在我知道为什么不是。这段代码有bug。尽管如此,这是针对这个特定问题的最佳概念,并且调试的效果非常好。

Snake Verde 回复 15分钟 前

这样做的一个缺点是丢失了返回类型提示。

0
killjoy 回答 15分钟 前

不使用其他模块,您可以使用__post_init__函数自动转换dict将值设置为正确的类型。此函数在__init__.

from dataclasses import dataclass, asdict


@dataclass
class Bar:
    fee: str
    far: str

@dataclass
class Foo:
    bar: Bar

    def __post_init__(self):
        if isinstance(self.bar, dict):
            self.bar = Bar(**self.bar)

foo = Foo(bar=Bar(fee="La", far="So"))

d= asdict(foo)
print(d)  # {'bar': {'fee': 'La', 'far': 'So'}}
o = Foo(**d)
print(o)  # Foo(bar=Bar(fee='La', far='So'))

该解决方案的另一个好处是能够使用非数据类对象。只要是str功能可以转换回来,这是公平的游戏。例如,它可以用来保持str字段为IP4Address内部。

Davos 回复 15分钟 前

这是最简单的解决方案,不依赖于自定义函数或外部库,因此更具可移植性。它还可以很好地处理集合,并用集合嵌套多个类。应标记为正确答案。

0
tikhonov_a 回答 15分钟 前

您可以使用棉花糖猫用于根据方案从dict创建数据类对象。此库中的Mixin添加了方便的from_dictto_dict数据类的方法:

from dataclasses import dataclass
from typing import List
from mashumaro import DataClassDictMixin

@dataclass
class Point(DataClassDictMixin):
     x: int
     y: int

@dataclass
class C(DataClassDictMixin):
     mylist: List[Point]

p = Point(10, 20)
tmp = {'x': 10, 'y': 20}
assert p.to_dict() == tmp
assert Point.from_dict(tmp) == p

c = C([Point(0, 0), Point(10, 4)])
tmp = {'mylist': [{'x': 0, 'y': 0}, {'x': 10, 'y': 4}]}
assert c.to_dict() == tmp
assert C.from_dict(tmp) == c
Giorgio Balestrieri 回复 15分钟 前

哇,太棒了。如果对msgpack和pyyaml的依赖是可选的,我可以看到它在某个时候被包含在标准库中。将序列化添加到数据类是一件轻而易举的事,这可能是首先使用它们的最常见原因之一。

tikhonov_a 回复 15分钟 前

@GiorgioBalestrieri迟做总比不做要好,但由于版本3.0,msgpack和pyyaml是可选的。

0
Martijn Pieters 回答 15分钟 前

如果你的目标是生产JSON从和到现有、预定义数据类,然后只编写自定义编码器和解码器挂钩。不要使用dataclasses.asdict()在这里,而不是记录在JSON中对原始数据类的(安全)引用。
jsonpickle不安全,因为它存储对的引用任意的Python对象并将数据传递给其构造函数。有了这些引用,我可以让jsonpickle引用内部Python数据结构,并随意创建和执行函数、类和模块。但这并不意味着你不能不安全地处理此类引用。在使用之前,只需验证您只导入(而不是调用),然后验证该对象是实际的数据类类型。
该框架可以变得足够通用,但仍然仅限于JSON可序列化类型dataclass-基于实例:

import dataclasses
import importlib
import sys

def dataclass_object_dump(ob):
    datacls = type(ob)
    if not dataclasses.is_dataclass(datacls):
        raise TypeError(f"Expected dataclass instance, got '{datacls!r}' object")
    mod = sys.modules.get(datacls.__module__)
    if mod is None or not hasattr(mod, datacls.__qualname__):
        raise ValueError(f"Can't resolve '{datacls!r}' reference")
    ref = f"{datacls.__module__}.{datacls.__qualname__}"
    fields = (f.name for f in dataclasses.fields(ob))
    return {**{f: getattr(ob, f) for f in fields}, '__dataclass__': ref}

def dataclass_object_load(d):
    ref = d.pop('__dataclass__', None)
    if ref is None:
        return d
    try:
        modname, hasdot, qualname = ref.rpartition('.')
        module = importlib.import_module(modname)
        datacls = getattr(module, qualname)
        if not dataclasses.is_dataclass(datacls) or not isinstance(datacls, type):
            raise ValueError
        return datacls(**d)
    except (ModuleNotFoundError, ValueError, AttributeError, TypeError):
        raise ValueError(f"Invalid dataclass reference {ref!r}") from None

这使用JSON RPC样式类提示命名数据类,并在加载时验证它仍然是具有相同字段的数据类。没有对字段的值进行类型检查(因为这是一个完全不同的问题)。
使用这些作为defaultobject_hook的参数json.dump[s]()json.dump[s]():

>>> print(json.dumps(c, default=dataclass_object_dump, indent=4))
{
    "mylist": [
        {
            "x": 0,
            "y": 0,
            "__dataclass__": "__main__.Point"
        },
        {
            "x": 10,
            "y": 4,
            "__dataclass__": "__main__.Point"
        }
    ],
    "__dataclass__": "__main__.C"
}
>>> json.loads(json.dumps(c, default=dataclass_object_dump), object_hook=dataclass_object_load)
C(mylist=[Point(x=0, y=0), Point(x=10, y=4)])
>>> json.loads(json.dumps(c, default=dataclass_object_dump), object_hook=dataclass_object_load) == c
True

或创建JSONEncoderJSONDecoder具有相同钩子的类。
您也可以使用单独的注册表来映射允许的类型名,而不是使用完全限定的模块名和类名;检查注册表中的编码和解码,确保在开发时不会忘记注册数据类。