目前,我像这样使用DTO(数据传输对象)。
class Test1:
def __init__(self,
user_id: int = None,
body: str = None):
self.user_id = user_id
self.body = body
示例代码很小,但是当对象规模增长时,我必须定义每个变量。
在深入研究时发现python 3.7支持dataclass
下面的代码是DTO使用的数据类。
from dataclasses import dataclass
@dataclass
class Test2:
user_id: int
body: str
在这种情况下,如何允许在class Test2
中传递更多未定义的参数?
如果我使用Test1
,这很容易。只需将**kwargs(asterisk)
添加到__init__
class Test1:
def __init__(self,
user_id: int = None,
body: str = None,
**kwargs):
self.user_id = user_id
self.body = body
但是使用数据类,找不到实现它的任何方法。
这里有什么解决办法吗?
谢谢。
编辑
class Test1:
def __init__(self,
user_id: str = None,
body: str = None):
self.user_id = user_id
self.body = body
if __name__ == '__main__':
temp = {'user_id': 'hide', 'body': 'body test'}
t1 = Test1(**temp)
print(t1.__dict__)
结果:{'user_id': 'hide', 'body': 'body test'}
如您所知,我要插入字典类型为-> **temp
在数据类中使用星号的原因相同。
我必须将字典类型传递给init类。
这里有什么主意吗?
答案 0 :(得分:4)
数据类仅依赖于__init__
方法,因此您可以在__new__
方法中随意更改类。
from dataclasses import dataclass
@dataclass
class Container:
user_id: int
body: str
def __new__(cls, *args, **kwargs):
try:
initializer = cls.__initializer
except AttributeError:
# Store the original init on the class in a different place
cls.__initializer = initializer = cls.__init__
# replace init with something harmless
cls.__init__ = lambda *a, **k: None
# code from adapted from Arne
added_args = {}
for name in list(kwargs.keys()):
if name not in cls.__annotations__:
added_args[name] = kwargs.pop(name)
ret = object.__new__(cls)
initializer(ret, **kwargs)
# ... and add the new ones by hand
for new_name, new_val in added_args.items():
setattr(ret, new_name, new_val)
return ret
if __name__ == "__main__":
params = {'user_id': 1, 'body': 'foo', 'bar': 'baz', 'amount': 10}
c = Container(**params)
print(c.bar) # prints: 'baz'
print(c.body) # prints: 'baz'`
答案 1 :(得分:3)
数据类的基本用例是提供一个将参数映射到属性的容器。如果您有未知的参数,则在类创建过程中将不知道各自的属性。
如果您在初始化期间知道哪些参数未知,可以手动解决该问题,方法是手动将它们发送到一个包罗万象的属性:
from dataclasses import dataclass, field
@dataclass
class Container:
user_id: int
body: str
meta: field(default_factory=dict)
# usage:
obligatory_args = {'user_id': 1, 'body': 'foo'}
other_args = {'bar': 'baz', 'amount': 10}
c = Container(**obligatory_args, meta=other_args)
print(c.meta['bar']) # prints: 'baz'
但是在这种情况下,您仍然需要查看字典,并且无法按名称访问参数,即c.bar
不起作用。
如果您关心按名称访问属性,或者在初始化期间无法区分已知参数和未知参数,那么您的最后一招就是不重写__init__
(这几乎违背了使用{{1 }}首先是写一个dataclasses
:
@classmethod
答案 2 :(得分:2)
这是我使用过的一种简洁的变体。
from dataclasses import dataclass, field
from typing import Optional, Dict
@dataclass
class MyDataclass:
data1: Optional[str] = None
data2: Optional[Dict] = None
data3: Optional[Dict] = None
kwargs: field(default_factory=dict) = None
def __post_init__(self):
[setattr(self, k, v) for k, v in self.kwargs.items()]
其工作原理如下:
>>> data = MyDataclass(data1="data1", kwargs={"test": 1, "test2": 2})
>>> data.test
1
>>> data.test2
2
但是请注意,数据类似乎不知道它具有以下新属性:
>>> from dataclasses import asdict
>>> asdict(data)
{'data1': 'data1', 'data2': None, 'data3': None, 'kwargs': {'test': 1, 'test2': 2}}
这意味着必须知道密钥。这适用于我的用例,也可能适用于其他用例。