动态添加字段到数据类对象

问题描述 投票:0回答:2

我正在编写一个库来访问 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}
python python-3.x dynamic field python-dataclasses
2个回答
46
投票

您可以使用

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'}

11
投票

更新(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'}

如果您仍然喜欢使用数据类来建模您的数据,我在下面包含了我的原始答案,与过去几年相比大部分没有变化。

基准

以下结果是在配备 M1 芯片、Python 3.10.4 和

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!'}


免责声明:我是这个库的创建者(和维护者)。

© www.soinside.com 2019 - 2024. All rights reserved.