mba*_*rov 38 python python-3.x python-dataclasses
3.7中的标准库可以递归地将数据类转换为dict(来自docs的示例):
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
Run Code Online (Sandbox Code Playgroud)
我正在寻找一种方法,在存在嵌套时将字典转回数据类.喜欢的东西,C(**tmp)只有工作,如果数据类的字段是简单类型,而不是自己的数据类.我熟悉jsonpickle,但它带有一个突出的安全警告.
Kon*_*łas 27
我是这个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)
Run Code Online (Sandbox Code Playgroud)
此外还dacite支持以下功能:
......经过充分测试 - 100%的代码覆盖率!
要安装dacite,只需使用pip(或pipenv):
$ pip install dacite
Run Code Online (Sandbox Code Playgroud)
meo*_*dog 17
下面是CPython的实现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)
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
综观原代码,只有第一条需要进行修改,以使用该包装,与其他条款只处理集装箱的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)
Run Code Online (Sandbox Code Playgroud)
进口:
from dataclasses import dataclass, fields, is_dataclass
# thanks to Patrick Haugh
from typing import *
# deepcopy
import copy
Run Code Online (Sandbox Code Playgroud)
使用的功能:
# 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)
Run Code Online (Sandbox Code Playgroud)
使用示例数据类进行测试:
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'>
Run Code Online (Sandbox Code Playgroud)
结果如预期.
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)
Run Code Online (Sandbox Code Playgroud)
使用的功能:
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)
Run Code Online (Sandbox Code Playgroud)
测试:
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)])
Run Code Online (Sandbox Code Playgroud)
再次如预期.
Fra*_*ank 13
我还没有看到提到的一个可能的解决方案是使用dataclasses-json. 该库提供了dataclass实例与 JSON 之间的转换,也提供了实例与 JSON 之间的转换dict(例如dacite和mashumaro,这在之前的答案中已建议)。
dataclasses-json@dataclass_json除了 之外,还需要用 来装饰类@dataclass。然后,装饰类获得几个成员函数,用于与 JSON 和 to/from 之间的转换dict:
from_dict(...)from_json(...)to_dict(...)to_json(...)这是问题中原始代码的稍作修改的版本。我添加了所需的@dataclass_json装饰器 和asserts,以便从 s 转换为和dict的实例:PointC
from dataclasses import dataclass, asdict
from dataclasses_json import dataclass_json
from typing import List
@dataclass_json
@dataclass
class Point:
x: int
y: int
@dataclass_json
@dataclass
class C:
mylist: List[Point]
p = Point(10, 20)
assert asdict(p) == {'x': 10, 'y': 20}
assert p == Point.from_dict({'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
assert c == C.from_dict(tmp)
Run Code Online (Sandbox Code Playgroud)
小智 8
您可以使用mashumaro根据该方案从字典创建数据类对象。该库中的Mixin 为数据类添加了方便from_dict和to_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
Run Code Online (Sandbox Code Playgroud)
不使用附加模块,您可以利用该__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'))
Run Code Online (Sandbox Code Playgroud)
此解决方案具有能够使用非数据类对象的额外好处。只要它的str功能可以转换回来,就是公平的游戏。例如,它可用于将str字段保留为IP4Address内部。
它只需要五个班轮即可:
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
Run Code Online (Sandbox Code Playgroud)
用法示例:
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))
Run Code Online (Sandbox Code Playgroud)
完整代码,包括去往/来自json的代码,请参见gist:https : //gist.github.com/gatopeich/1efd3e1e4269e1e98fae9983bb914f22
如果您的目标是从现有的预定义数据类生成JSON,那么只需编写自定义编码器和解码器挂钩。不要在这里使用,而是在 JSON 中记录对原始数据类的(安全)引用。dataclasses.asdict()
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
Run Code Online (Sandbox Code Playgroud)
这使用JSON-RPC 样式的类提示来命名数据类,并在加载时验证它仍然是具有相同字段的数据类。没有对字段的值进行类型检查(因为那是完全不同的鱼)。
使用这些作为default和object_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
Run Code Online (Sandbox Code Playgroud)
或使用相同的钩子创建JSONEncoder和JSONDecoder类的实例。
除了使用完全限定的模块名和类名之外,您还可以使用单独的注册表来映射允许的类型名;检查编码注册表,并再次检查解码以确保您在开发时不会忘记注册数据类。
| 归档时间: |
|
| 查看次数: |
10974 次 |
| 最近记录: |