给定如下数据类:
class MessageHeader(BaseModel):
message_id: uuid.UUID
def dict(self, **kwargs):
return json.loads(self.json())
当我在
dict
上拨打 MessageHeader
时,我想获取字符串文字的字典
字典的期望结果如下:
{'message_id': '383b0bfc-743e-4738-8361-27e6a0753b5a'}
我想避免使用像
pydantic
这样的第3方库&我不想使用json.loads(self.json())
,因为有额外的往返
有没有更好的方法将数据类转换为带有上面字符串文字的字典?
dataclasses.asdict
:
from dataclasses import dataclass, asdict
class MessageHeader(BaseModel):
message_id: uuid.UUID
def dict(self):
return {k: str(v) for k, v in asdict(self).items()}
如果您确定您的类只有字符串值,您可以完全跳过字典理解:
class MessageHeader(BaseModel):
message_id: uuid.UUID
dict = asdict
使用此解决方案时要小心,因为如果您在
dataclass
之后扩展 __init__
对象 - asdict
将忽略任何新添加的内容!使用 vars(x)
或 x.__dict__
让它始终返回最新的字典。
为了绝对的纯粹、不掺假的速度和无限的效率,甚至可以让像Chuck Norris这样的人停下来,无助地敬畏地看着,我谦虚地推荐这个计划得非常好- out 方法与
__dict__
:
def dict(self):
_dict = self.__dict__.copy()
_dict['message_id'] = str(_dict['message_id'])
return _dict
对于定义
__slots__
属性 的类(例如 @dataclass(slots=True)
),上述方法很可能不起作用,因为 __dict__
属性在类实例上不可用。在这种情况下,如下所示的高效“登月”方法可能是可行的:
def dict(self):
body_lines = ','.join(f"'{f}':" + (f'str(self.{f})' if f == 'message_id'
else f'self.{f}') for f in self.__slots__)
# Compute the text of the entire function.
txt = f'def dict(self):\n return {{{body_lines}}}'
ns = {}
exec(txt, locals(), ns)
_dict_fn = self.__class__.dict = ns['dict']
return _dict_fn(self)
万一有人现在在座位边缘摇摇欲坠(我知道,这真的令人难以置信,突破级别的东西)-我已经通过下面的
timeit
模块添加了我的个人计时,希望如此在性能方面更加清晰。
仅供参考,纯
__dict__
的方法不可避免地比dataclasses.asdict()
快很多。
注意:尽管
在这种特殊情况下效果更好,但__dict__
可能更适合复合字典,例如具有嵌套数据类的字典,或具有可变类型(例如dataclasses.asdict()
或dict
)的值。list
from dataclasses import dataclass, asdict, field
from uuid import UUID, uuid4
class DictMixin:
"""Mixin class to add a `dict()` method on classes that define a __slots__ attribute"""
def dict(self):
body_lines = ','.join(f"'{f}':" + (f'str(self.{f})' if f == 'message_id'
else f'self.{f}') for f in self.__slots__)
# Compute the text of the entire function.
txt = f'def dict(self):\n return {{{body_lines}}}'
ns = {}
exec(txt, locals(), ns)
_dict_fn = self.__class__.dict = ns['dict']
return _dict_fn(self)
@dataclass
class MessageHeader:
message_id: UUID = field(default_factory=uuid4)
string: str = 'a string'
integer: int = 1000
floating: float = 1.0
def dict1(self):
_dict = self.__dict__.copy()
_dict['message_id'] = str(_dict['message_id'])
return _dict
def dict2(self):
return {k: str(v) if k == 'message_id' else v
for k, v in self.__dict__.items()}
def dict3(self):
return {k: str(v) if k == 'message_id' else v
for k, v in asdict(self).items()}
@dataclass(slots=True)
class MessageHeaderWithSlots(DictMixin):
message_id: UUID = field(default_factory=uuid4)
string: str = 'a string'
integer: int = 1000
floating: float = 1.0
def dict2(self):
return {k: str(v) if k == 'message_id' else v
for k, v in asdict(self).items()}
if __name__ == '__main__':
from timeit import timeit
header = MessageHeader()
header_with_slots = MessageHeaderWithSlots()
n = 10000
print('dict1(): ', timeit('header.dict1()', number=n, globals=globals()))
print('dict2(): ', timeit('header.dict2()', number=n, globals=globals()))
print('dict3(): ', timeit('header.dict3()', number=n, globals=globals()))
print('slots -> dict(): ', timeit('header_with_slots.dict()', number=n, globals=globals()))
print('slots -> dict2(): ', timeit('header_with_slots.dict2()', number=n, globals=globals()))
print()
dict__ = header.dict1()
print(dict__)
asdict__ = header.dict3()
print(asdict__)
assert isinstance(dict__['message_id'], str)
assert isinstance(dict__['integer'], int)
assert header.dict1() == header.dict2() == header.dict3()
assert header_with_slots.dict() == header_with_slots.dict2()
我的 Mac M1 笔记本电脑上的结果:
dict1(): 0.005992999998852611
dict2(): 0.00800508284009993
dict3(): 0.07069579092785716
slots -> dict(): 0.00583599996753037
slots -> dict2(): 0.07395245810039341
{'message_id': 'b4e17ef9-1a58-4007-9cef-39158b094da2', 'string': 'a string', 'integer': 1000, 'floating': 1.0}
{'message_id': 'b4e17ef9-1a58-4007-9cef-39158b094da2', 'string': 'a string', 'integer': 1000, 'floating': 1.0}
注意: 对于
DictMixin
(命名为SerializableMixin
)更“完整”的实现,请查看相关答案我也添加了。
这是“dataclass to dict”的最佳谷歌结果,上面的答案过于复杂。您可能正在寻找这个:
from dataclasses import dataclass
@dataclass
class MessageHeader():
uuid: str = "abcd"
vars(MessageHeader()) # or MessageHeader().__dict__
受到 @rv.kvetch 的回答的启发,我编写了这个装饰器,它将根据类定义动态生成
asdict
方法的代码。它还支持子类化,这意味着子类将继承超类的属性。
装饰者:
import typing
def generate_dict_method(
__source: typing.Literal["slots", "annotations"],
__name: str,
/,
**custom_mappings: typing.Callable[[typing.Any], typing.Any]
):
if custom_mappings is None:
custom_mappings = dict()
def decorator(cls):
attributes = set()
for mc in cls.__mro__:
if __source == 'annotations':
attrs = getattr(mc, "__annotations__", None)
if attrs:
attrs = attrs.keys()
elif __source == "slots":
attrs = getattr(mc, "__slots__", None)
else:
raise NotImplementedError(__source)
if attrs:
attributes.update(attrs)
if not attributes:
raise RuntimeError(
f"Unable to generate `{__name}` method for `{cls.__qualname__}` class: "
"no attributes found."
)
funclocals = {}
mapping_to_funcname = {}
for attrname, f in custom_mappings.items():
funcname = f'__parse_{attrname}'
funclocals[funcname] = f
mapping_to_funcname[attrname] = funcname
body_lines = ','.join([
f'"{attrname}": ' + (f'self.{attrname}' if attrname not in custom_mappings
else f'{mapping_to_funcname[attrname]}(self.{attrname})')
for attrname in attributes
])
txt = f'def {__name}(self):\n return {{{body_lines}}}'
d = dict()
exec(txt, funclocals, d)
setattr(cls, __name, d[__name])
return cls
return decorator
用途:
from dataclasses import dataclass
import json
@dataclass(slots=True, kw_only=True)
class TestBase:
i1: int
i2: int
@generate_dict_method("annotations", "asdict", d=(lambda x: "FUNNY" + json.dumps(x) + "JSON"))
@dataclass(slots=True, kw_only=True)
class Test(TestBase):
i: int
b: bool
s: str
d: dict
a = Test(i=1, b=True, s="test", d={"test": "test"}, i1=2, i2=3)
print(a.asdict())
输出:
{'d': 'FUNNY{"test": "test"}JSON', 'i': 1, 'i1': 2, 'b': True, 's': 'test', 'i2': 3}
如您所见,您只需为
**custom_mappings
参数和属性名称提供自定义解析器。这样您就可以以任何您认为合适的方式改变属性。
在您的情况下,您可以为
str
属性提供 message_id
函数。