如何将Python数据类转换为字符串文字字典?

Unk*_*own 57 python json dictionary python-dataclasses

给定如下数据类:

class MessageHeader(BaseModel):
    message_id: uuid.UUID

    def dict(self, **kwargs):
        return json.loads(self.json())
Run Code Online (Sandbox Code Playgroud)

dict当我调用时,我想获得一个字符串文字的字典。MessageHeader 字典的期望结果如下所示:

{'message_id': '383b0bfc-743e-4738-8361-27e6a0753b5a'}
Run Code Online (Sandbox Code Playgroud)

我想避免使用第三方库,例如pydantic& 我不想使用json.loads(self.json()),因为有额外的往返

有没有更好的方法将数据类转换为带有上面字符串文字的字典?

Mad*_*ist 83

您可以使用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()}
Run Code Online (Sandbox Code Playgroud)

如果您确定您的类只有字符串值,您可以完全跳过字典理解:

class MessageHeader(BaseModel):
    message_id: uuid.UUID

    dict = asdict
Run Code Online (Sandbox Code Playgroud)

  • 请注意,“asdict”仅返回在 init 处定义的键 (3认同)

rv.*_*tch 16

对于绝对纯粹、纯粹的速度无限的效率,这些甚至可能导致像 Chuck Norris 这样的人停下来,无助地敬畏地看着,我谦虚地推荐这种精心策划的方法__dict__

def dict(self):
    _dict = self.__dict__.copy()
    _dict['message_id'] = str(_dict['message_id'])
    return _dict
Run Code Online (Sandbox Code Playgroud)

__slots__对于定义attribute的类(例如 with @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)
Run Code Online (Sandbox Code Playgroud)

万一有人现在在座位边缘摇摇欲坠(我知道,这真的是令人难以置信的突破性的东西) - 我已经通过timeit下面的模块添加了我的个人计时,这应该有望在性能上提供更多的启示事物的方面。

仅供参考,使用 pure 的方法__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()
Run Code Online (Sandbox Code Playgroud)

我的 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}
Run Code Online (Sandbox Code Playgroud)

注意:DictMixin对于(命名为)的更“完整”实现SerializableMixin,请查看我还添加的相关答案。

  • @KarlKnechtel 我不*完全*确定,但我的钱花在了 `copy.deepcopy()` 调用上。如果您查看“asdict”的数据类源代码,您可以看到它对任何复杂或未知类型调用“deepcopy”,在本例中可能是“UUID”对象。 (2认同)

tbe*_*nst 5

这是“dataclass to dict”的最佳谷歌结果,上面的答案过于复杂。您可能正在寻找这个:

from dataclasses import dataclass
@dataclass
class MessageHeader():
    uuid: str = "abcd"
vars(MessageHeader()) # or MessageHeader().__dict__
Run Code Online (Sandbox Code Playgroud)

  • 'slots=True' 怎么样? (2认同)