来自嵌套dict的Python数据类
3.7中的标准库可以递归地将数据类转换为dict(文档中的示例): 我正在寻找一种方法,当存在嵌套时,将dict转换回数据类。类似于来自嵌套dict的Python数据类,python,python-3.x,python-dataclasses,Python,Python 3.x,Python Dataclasses,3.7中的标准库可以递归地将数据类转换为dict(文档中的示例): 我正在寻找一种方法,当存在嵌套时,将dict转换回数据类。类似于C(**tmp)的东西只有在数据类的字段是简单类型而不是数据类本身时才起作用。我熟悉[jsonpickle][1],但是它附带了一个显著的安全警告 编辑: 答案建议使用以下图书馆: 英安岩 mashumaro(我用了一段时间,效果很好,但很快就遇到了棘手的情况) pydantic(非常好用,文档非常好,角盒更少) [1] : 下面是关于asdict –或者具体地
C(**tmp)
的东西只有在数据类的字段是简单类型而不是数据类本身时才起作用。我熟悉[jsonpickle][1],但是它附带了一个显著的安全警告
编辑: 答案建议使用以下图书馆:
- 英安岩
- mashumaro(我用了一段时间,效果很好,但很快就遇到了棘手的情况)
- pydantic(非常好用,文档非常好,角盒更少) [1] :
- 下面是关于
asdict
–或者具体地说,它使用的内部递归辅助函数\u asdict\u internal
:
# 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\u 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)
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)])
print(c)
cd = todict(c)
print(cd)
# {'mylist': [{'x': 0, 'y': 0}, {'x': 10, 'y': 4}]}
print(cd.type)
# <class '__main__.C'>
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
使用的功能:
# 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)
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)
测试:
再次如预期。如果您的目标是从现有的预定义数据类生成JSON,那么只需编写自定义编码器和解码器挂钩。不要在这里使用
dataclasses.asdict()
,而是在JSON中记录对原始数据类的(安全)引用
jsonpickle
不安全,因为它存储对任意Python对象的引用,并将数据传递给它们的构造函数。有了这些引用,我可以让jsonpickle引用内部Python数据结构,并随意创建和执行函数、类和模块。但这并不意味着你不能不安全地处理这些推荐信。在使用该对象之前,只需验证您只导入(而不是调用),然后验证该对象是实际的数据类类型
该框架可以变得足够通用,但仍然仅限于JSON可序列化类型加上基于数据类的实例:
它用于命名数据类,并且在加载时验证它仍然是具有相同字段的数据类。没有对字段的值进行类型检查(因为这是一个完全不同的问题)
将它们用作json.dump[s]()
和json.dump[s]()
的default
和object\u hook
参数:
或者使用这些钩子创建和类的实例
您也可以使用单独的注册表来映射允许的类型名,而不是使用完全限定的模块名和类名;在编码时检查注册表,在解码时再次检查注册表,以确保您在开发时不会忘记注册数据类。您可以根据方案从dict创建数据类对象。此库中的Mixin将方便的from_dict
和to_dict
方法添加到数据类:
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'>
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
我是-的作者,该工具简化了从字典创建数据类的过程
此库只有一个来自_dict的函数-这是一个简单的用法示例:
从数据类导入数据类
从英安岩进口自
@数据类
类用户:
姓名:str
年龄:整数
_是否处于活动状态:bool
数据={
“姓名”:“约翰”,
年龄:30岁,,
'is_active':True,
}
用户=来自dict(数据类=用户,数据=数据)
assert user==user(name='john',age=30,is_active=True)
此外,dacite
还支持以下功能:
- 嵌套结构
- (基本)类型检查
- 可选字段(即键入。可选)
- 工会
- 收藏
- 价值观的铸造与转化
- 字段名称的重新映射
。。。而且它经过了很好的测试——100%的代码覆盖率
要安装dacite,只需使用pip(或pipenv):
只需要一艘五轮邮轮:
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:是一个库,可以提供帮助。下面是一个最小使用量示例:
导入json
从数据类导入数据类
从键入导入列表、NamedTuple、可选、任意
从未精简导入类型\u选中\u构造函数
@类型\u检查\u构造函数(跳过=真)
@数据类
班心:
重量(单位:kg):浮子
静止时的脉冲:int
@类型\u检查\u构造函数(跳过=真)
@数据类
类人:
id:int
姓名:str
尼克:可选[str]
心:心
好友ID:List[int]
tobias_dict=json.load(“”)
{
“id”:1,
“姓名”:“托比亚斯”,
“心”:{
“重量单位为千克”:0.31,
“静止时脉冲”:52
},
“朋友ID”:[2,3,4,5]
}''')
托比亚斯=人类(**托比亚斯)
就是这样做的。与其他库相比,它提供了一个更简单的接口(目前只有一个函数),并强调提供信息的错误消息。例如,给定一个类似
导入数据类
从键入import Optional开始,列出
@dataclasses.dataclass
类用户:
姓名:str
电话:可选[str]=无
任务:List[str]=dataclasses.field(默认工厂=List)
一个人会犯这样的错误
导入validobj
>>>validobj.parse_输入({
…“电话”:“555-1337-000”,“地址”:“Somewhereville”,“nme”:“Zahari”},用户
... )
回溯(最近一次呼叫最后一次):
...
ErrorKeysError:无法将值处理为“用户”,因为字段不匹配。
缺少以下必需的键:{'name'}。下列键未知:{'nme','address'}
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))
from dataclasses import dataclass
from typing import List
@dataclass
class CompositeDict:
def as_dict(self):
retval = dict()
for key, value in self.__dict__.items():
if key in self.__dataclass_fields__.keys():
if type(value) is list:
retval[key] = [item.as_dict() for item in value]
else:
retval[key] = value
return retval
@dataclass
class Point(CompositeDict):
x: int
y: int
@dataclass
class C(CompositeDict):
mylist: List[Point]
c = C([Point(0, 0), Point(10, 4)])
tmp = {'mylist': [{'x': 0, 'y': 0}, {'x': 10, 'y': 4}]}
assert c.as_dict() == tmp
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'))
from validated_dc import ValidatedDC
from dataclasses import dataclass
from typing import List, Union
@dataclass
class Foo(ValidatedDC):
foo: int
@dataclass
class Bar(ValidatedDC):
bar: Union[Foo, List[Foo]]
foo = {'foo': 1}
instance = Bar(bar=foo)
print(instance.get_errors()) # None
print(instance) # Bar(bar=Foo(foo=1))
list_foo = [{'foo': 1}, {'foo': 2}]
instance = Bar(bar=list_foo)
print(instance.get_errors()) # None
print(instance) # Bar(bar=[Foo(foo=1), Foo(foo=2)])