我正在编写一个库来访问 REST API。它返回带有用户对象的 json。我将其转换为 dict,然后将其转换为数据类对象。问题是并非所有字段都是固定的。我想动态添加其他字段(未在我的数据类中指定)。我可以简单地为我的对象分配值,但它们不会出现在对象表示中,并且
dataclasses.asdict
函数不会将它们添加到结果字典中:
from dataclasses import asdict, dataclass
@dataclass
class X:
i: int
x = X(i=42)
x.s = 'text'
x
# X(i=42)
x.s
# 'text'
asdict(x)
# {'i': 42}
make_dataclass
即时创建 X
:
from dataclasses import asdict, dataclass, make_dataclass
X = make_dataclass('X', [('i', int), ('s', str)])
x = X(i=42, s='text')
asdict(x)
# {'i': 42, 's': 'text'}
或者作为派生类:
from dataclasses import asdict, dataclass, make_dataclass
@dataclass
class X:
i: int
x = X(i=42)
x.__class__ = make_dataclass('Y', fields=[('s', str)], bases=(X,))
x.s = 'text'
asdict(x)
# {'i': 42, 's': 'text'}
更新(6/22):现在已经是 2022 年中期了,我想我应该用我一直在尝试的全新方法来刷新我的答案。我很高兴地宣布我最近发布了一个快速、现代的库,名为
dotwiz
。
dotwiz
库可以使用pip安装:
pip install dotwiz
这是我创建的一个小型帮助程序库,它使
dict
对象可以安全地通过点表示法访问 - 例如 a.b.c
而不是 a['b']['c']
。从个人测试和基准来看,它实际上比 make_dataclass
快很多- 下面有更多信息。 此外,还可以从
DotWiz
或
DotWizPlus
进行子类化,这可以从 PyCharm 等 IDE 中启用类型提示和自动完成提示。下面是一个简单的例子:
from dataclasses import asdict, make_dataclass
from dotwiz import DotWiz
class MyTypedWiz(DotWiz):
# add attribute names and annotations for better type hinting!
i: int
s: str
dw = MyTypedWiz(i=42, s='text')
print(dw)
# ✫(i=42, s='text')
print(dw.to_dict())
# {'i': 42, 's': 'text'}
如果您仍然喜欢使用数据类来建模您的数据,我在下面包含了我的原始答案,与过去几年相比大部分没有变化。
基准n=5000
迭代的 Mac Pro 上计时的。
创建或实例化对象:
$ python -m timeit -n 5000 -s "from dotwiz import DotWiz" -c "DotWiz(i=42, s='text')"
5000 loops, best of 5: 425 nsec per loop
$ python -m timeit -n 5000 -s "from dataclasses import make_dataclass" -c "X = make_dataclass('X', [('i', int), ('s', str)]); X(i=42, s='text')"
5000 loops, best of 5: 97.8 usec per loop
这些时间可能有所夸大,但在这种特殊情况下,看起来 DotWiz
比
make_dataclass
快约 250x。实际上,我想说平均速度大约快 100 倍。
通过点符号访问键:
$ python -m timeit -n 5000 -s "from dotwiz import DotWiz" -s "dw = DotWiz(i=42, s='text')" -c "dw.s.lower()"
5000 loops, best of 5: 39.7 nsec per loop
$ python -m timeit -n 5000 -s "from dataclasses import make_dataclass" -s "X = make_dataclass('X', [('i', int), ('s', str)])" -s "x = X(i=42, s='text')" -c "x.s.lower()"
5000 loops, best of 5: 39.9 nsec per loop
访问属性或键的时间看起来基本相同。
将对象序列化为 JSON:
$ python -m timeit -n 5000 -s "import json" -s "from dotwiz import DotWiz" -s "dw = DotWiz(i=42, s='text')" -c "json.dumps(dw)"
5000 loops, best of 5: 1.1 usec per loop
$ python -m timeit -n 5000 -s "import json" -s "from dotwiz import DotWiz" -s "dw = DotWiz(i=42, s='text')" -c "json.dumps(dw.to_dict())"
5000 loops, best of 5: 1.46 usec per loop
$ python -m timeit -n 5000 -s "import json" -s "from dataclasses import asdict, make_dataclass" -s "X = make_dataclass('X', [('i', int), ('s', str)])" -s "x = X(i=42, s='text')" -c "json.dumps(asdict(x))"
5000 loops, best of 5: 2.87 usec per loop
因此,与 DotWiz
实例相比,序列化 dataclass
对象实际上看起来快了2.5 倍
。原答案
dataclasses
中的属性。是的,常规属性应该足够好 - 尽管您必须在
__post_init__
中声明字段,这有点不方便。如果您想为属性设置默认值,以便在创建对象后立即访问 getter 效果很好,并且如果您还希望能够通过构造函数设置默认值,则可以使用称为字段属性的概念;一些库,例如
dataclass-wizard 提供 全力支持。
用法示例:
from dataclasses import asdict, dataclass
from typing import Optional
from dataclass_wizard import property_wizard
@dataclass
class X(metaclass=property_wizard):
i: int
s: Optional[str] = None
@property
def _s(self):
"""Returns a title-cased value, i.e. `stRiNg` -> `String`"""
return self._s.title() if self._s else None
@_s.setter
def _s(self, s: str):
"""Reverses a string, i.e. `olleH` -> `Hello` """
self._s = s[::-1] if s else None
x = X(i=42)
x
# X(i=42, s=None)
assert x.s is None # True
x.s = '!emordnilap'
x
# X(i=42, s='Palindrome!')
x.s
# 'Palindrome!'
asdict(x)
# {'i': 42, 's': 'Palindrome!'}
免责声明:我是这个库的创建者(和维护者)。