注意,这与How to get @property methods in asdict?类似。
我有一个(冻结的)嵌套数据结构,如下所示。定义了一些(纯粹)依赖于字段的属性。
import copy
import dataclasses
import json
from dataclasses import dataclass
@dataclass(frozen=True)
class Bar:
x: int
y: int
@property
def z(self):
return self.x + self.y
@dataclass(frozen=True)
class Foo:
a: int
b: Bar
@property
def c(self):
return self.a + self.b.x - self.b.y
我可以按如下方式序列化数据结构:
class CustomEncoder(json.JSONEncoder):
def default(self, o):
if dataclasses and dataclasses.is_dataclass(o):
return dataclasses.asdict(o)
return json.JSONEncoder.default(self, o)
foo = Foo(1, Bar(2,3))
print(json.dumps(foo, cls=CustomEncoder))
# Outputs {"a": 1, "b": {"x": 2, "y": 3}}
然而,我也想序列化属性(@property
)。注意我不想使用__post_init__
将属性转换为字段,因为我想保持数据类的冻结。I do not want to use obj.__setattr__
to work around the frozen fields.我也不想预先计算类外的属性值并将其作为字段传递。
我目前使用的解决方案是显式写出每个对象是如何序列化的,如下所示:
class CustomEncoder2(json.JSONEncoder):
def default(self, o):
if isinstance(o, Foo):
return {
"a": o.a,
"b": o.b,
"c": o.c
}
elif isinstance(o, Bar):
return {
"x": o.x,
"y": o.y,
"z": o.z
}
return json.JSONEncoder.default(self, o)
foo = Foo(1, Bar(2,3))
print(json.dumps(foo, cls=CustomEncoder2))
# Outputs {"a": 1, "b": {"x": 2, "y": 3, "z": 5}, "c": 0} as desired
对于一些嵌套层次来说,这是可以管理的,但是我希望有一个更通用的解决方案,例如,这里有一个(hacky)解决方案,它从dataclasses库中对_asdict_inner实现进行monkey-patches。
def custom_asdict_inner(obj, dict_factory):
if dataclasses._is_dataclass_instance(obj):
result = []
for f in dataclasses.fields(obj):
value = custom_asdict_inner(getattr(obj, f.name), dict_factory)
result.append((f.name, value))
# Inject this one-line change
result += [(prop, custom_asdict_inner(getattr(obj, prop), dict_factory)) for prop in dir(obj) if not prop.startswith('__')]
return dict_factory(result)
elif isinstance(obj, tuple) and hasattr(obj, '_fields'):
return type(obj)(*[custom_asdict_inner(v, dict_factory) for v in obj])
elif isinstance(obj, (list, tuple)):
return type(obj)(custom_asdict_inner(v, dict_factory) for v in obj)
elif isinstance(obj, dict):
return type(obj)((custom_asdict_inner(k, dict_factory),
custom_asdict_inner(v, dict_factory))
for k, v in obj.items())
else:
return copy.deepcopy(obj)
dataclasses._asdict_inner = custom_asdict_inner
class CustomEncoder3(json.JSONEncoder):
def default(self, o):
if dataclasses and dataclasses.is_dataclass(o):
return dataclasses.asdict(o)
return json.JSONEncoder.default(self, o)
foo = Foo(1, Bar(2,3))
print(json.dumps(foo, cls=CustomEncoder3))
# Outputs {"a": 1, "b": {"x": 2, "y": 3, "z": 5}, "c": 0} as desired
有没有推荐的方法来实现我正在尝试做的事情?
3条答案
按热度按时间ev7lccsx1#
这似乎与一个方便的
dataclass
特性相矛盾:如果你没有找到任何相关的pypi软件包,你可以像这样添加一个2行程序:
然后,您可以用一种自定义但简短的方式指定您想要在dicts中使用的对象:
:
qq24tv8q2#
据我所知,没有“推荐”的方法来包括它们。
这里有一个看起来很有效的东西,我认为它可以满足你的许多需求。它定义了一个自定义编码器,当对象是
dataclass
时,它调用自己的_asdict()
方法,而不是在客户编码器中修补(私有)dataclasses._asdict_inner()
函数和encapsulates(捆绑)代码。和您一样,我使用
dataclasses.asdict()
的当前实现作为指南/模板,因为您所要求的基本上只是它的定制版本。每个property
字段的当前值都是通过调用其__get__
方法获得的。输出:
koaltpgm3#
如果适用于您的解决方案,您可以在基类上定义属性,并让具体类实现这些属性,这适用于
asdict
。