python3 带有 **kwargs(星号)的数据类
python3 dataclass with **kwargs(asterisk)
目前我是这样使用DTO(Data Transfer Object)的。
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使用的数据class.
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,找不到任何实现方法。
这里有什么解决办法吗?
谢谢。
编辑
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
在数据中使用星号的原因class 相同。
我必须将字典类型传递给 class init.
有什么想法吗?
dataclasses 的基本用例是提供一个将参数映射到属性的容器。如果您有未知参数,则在 class 创建期间无法知道各自的属性。
如果您知道在初始化期间哪些参数是未知的,您可以通过手动将它们发送到一个包罗万象的属性来解决它:
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__
(这几乎违背了使用 dataclasses
首先)正在写一个 @classmethod
:
from dataclasses import dataclass
from inspect import signature
@dataclass
class Container:
user_id: int
body: str
@classmethod
def from_kwargs(cls, **kwargs):
# fetch the constructor's signature
cls_fields = {field for field in signature(cls).parameters}
# split the kwargs into native ones and new ones
native_args, new_args = {}, {}
for name, val in kwargs.items():
if name in cls_fields:
native_args[name] = val
else:
new_args[name] = val
# use the native ones to create the class ...
ret = cls(**native_args)
# ... and add the new ones by hand
for new_name, new_val in new_args.items():
setattr(ret, new_name, new_val)
return ret
用法:
params = {'user_id': 1, 'body': 'foo', 'bar': 'baz', 'amount': 10}
Container(**params) # still doesn't work, raises a TypeError
c = Container.from_kwargs(**params)
print(c.bar) # prints: 'baz'
Dataclass 仅依赖于 __init__
方法,因此您可以在 __new__
方法中自由更改 class。
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'`
这是我使用的一个巧妙的变体。
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}}
这意味着必须知道密钥。这适用于我的用例,也可能适用于其他用例。
from dataclasses import make_dataclass
Clas = make_dataclass('A',
['d'],
namespace={
'__post_init__': lambda self: self.__dict__.update(self.d)
})
d = {'a':1, 'b': 2}
instance = Clas(d)
instance.a
的变体:
您可以使用以下方法:
- 通过
kwargs
参数添加额外的属性:MyDataclass(xx, yy, kwargs={...}
)
kwargs
是一个 dataclasses.InitVar
,然后在您的数据 __post_init__
中处理 class
- 您可以使用
instance.__dict__
访问所有值(因为 asdict
不会检测通过 kwargs=...
添加的属性
这只会使用来自数据classes 的本机功能并且继承此 class 仍然有效。
from dataclasses import InitVar, asdict, dataclass
from typing import Dict, Optional
@dataclass
class MyDataclass:
data1: Optional[str] = None
data2: Optional[Dict] = None
data3: Optional[Dict] = None
kwargs: InitVar[Optional[Dict[str, Any]]] = None
def __post_init__(self, kwargs: Optional[Dict[str, Any]]) -> None:
if kwargs:
for k, v in kwargs.items():
setattr(self, k, v)
data = MyDataclass(data1="data_nb_1", kwargs={"test1": 1, "test2": 2})
print(data, "-", data.data1, "-", data.test1)
# MyDataclass(data1='data_nb_1', data2=None, data3=None) - data1 - 1
print(asdict(data))
# {'data1': 'data_nb_1', 'data2': None, 'data3': None}
print(data.__dict__)
# {'data1': 'data_nb_1', 'data2': None, 'data3': None, 'test1': 1, 'test2': 2}
如果你真的需要使用 asdict
来获取作为 kwargs 传递的属性,你可以开始使用 dataclasses 中的私有属性来破解 asdict
:
from dataclasses import _FIELD, _FIELDSInitVar, asdict, dataclass, field
from typing import Dict, Optional
@dataclass
class MyDataclass:
data1: Optional[str] = None
data2: Optional[Dict] = None
data3: Optional[Dict] = None
kwargs: InitVar[Optional[Dict[str, Any]]] = None
def __post_init__(self, kwargs: Optional[Dict[str, Any]]) -> None:
if kwargs:
for k, v in kwargs.items():
setattr(self, k, v)
self._add_to_asdict(k)
def _add_to_asdict(self, attr:str) -> None:
"""Add an attribute to the list of keys returned by asdict"""
f = field(repr=True)
f.name = attr
f._field_type = _FIELD
getattr(self, _FIELDS)[attr] = f
data = MyDataclass(data1="data_nb_1", kwargs={"test1": 1, "test2": 2})
print(asdict(data))
# {'data1': 'data_nb_1', 'data2': None, 'data3': None, 'test1': 1, 'test2': 2}
目前我是这样使用DTO(Data Transfer Object)的。
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使用的数据class.
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,找不到任何实现方法。
这里有什么解决办法吗?
谢谢。
编辑
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
在数据中使用星号的原因class 相同。
我必须将字典类型传递给 class init.
有什么想法吗?
dataclasses 的基本用例是提供一个将参数映射到属性的容器。如果您有未知参数,则在 class 创建期间无法知道各自的属性。
如果您知道在初始化期间哪些参数是未知的,您可以通过手动将它们发送到一个包罗万象的属性来解决它:
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__
(这几乎违背了使用 dataclasses
首先)正在写一个 @classmethod
:
from dataclasses import dataclass
from inspect import signature
@dataclass
class Container:
user_id: int
body: str
@classmethod
def from_kwargs(cls, **kwargs):
# fetch the constructor's signature
cls_fields = {field for field in signature(cls).parameters}
# split the kwargs into native ones and new ones
native_args, new_args = {}, {}
for name, val in kwargs.items():
if name in cls_fields:
native_args[name] = val
else:
new_args[name] = val
# use the native ones to create the class ...
ret = cls(**native_args)
# ... and add the new ones by hand
for new_name, new_val in new_args.items():
setattr(ret, new_name, new_val)
return ret
用法:
params = {'user_id': 1, 'body': 'foo', 'bar': 'baz', 'amount': 10}
Container(**params) # still doesn't work, raises a TypeError
c = Container.from_kwargs(**params)
print(c.bar) # prints: 'baz'
Dataclass 仅依赖于 __init__
方法,因此您可以在 __new__
方法中自由更改 class。
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'`
这是我使用的一个巧妙的变体。
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}}
这意味着必须知道密钥。这适用于我的用例,也可能适用于其他用例。
from dataclasses import make_dataclass
Clas = make_dataclass('A',
['d'],
namespace={
'__post_init__': lambda self: self.__dict__.update(self.d)
})
d = {'a':1, 'b': 2}
instance = Clas(d)
instance.a
您可以使用以下方法:
- 通过
kwargs
参数添加额外的属性:MyDataclass(xx, yy, kwargs={...}
) kwargs
是一个dataclasses.InitVar
,然后在您的数据__post_init__
中处理 class- 您可以使用
instance.__dict__
访问所有值(因为asdict
不会检测通过kwargs=...
添加的属性
这只会使用来自数据classes 的本机功能并且继承此 class 仍然有效。
from dataclasses import InitVar, asdict, dataclass
from typing import Dict, Optional
@dataclass
class MyDataclass:
data1: Optional[str] = None
data2: Optional[Dict] = None
data3: Optional[Dict] = None
kwargs: InitVar[Optional[Dict[str, Any]]] = None
def __post_init__(self, kwargs: Optional[Dict[str, Any]]) -> None:
if kwargs:
for k, v in kwargs.items():
setattr(self, k, v)
data = MyDataclass(data1="data_nb_1", kwargs={"test1": 1, "test2": 2})
print(data, "-", data.data1, "-", data.test1)
# MyDataclass(data1='data_nb_1', data2=None, data3=None) - data1 - 1
print(asdict(data))
# {'data1': 'data_nb_1', 'data2': None, 'data3': None}
print(data.__dict__)
# {'data1': 'data_nb_1', 'data2': None, 'data3': None, 'test1': 1, 'test2': 2}
如果你真的需要使用 asdict
来获取作为 kwargs 传递的属性,你可以开始使用 dataclasses 中的私有属性来破解 asdict
:
from dataclasses import _FIELD, _FIELDSInitVar, asdict, dataclass, field
from typing import Dict, Optional
@dataclass
class MyDataclass:
data1: Optional[str] = None
data2: Optional[Dict] = None
data3: Optional[Dict] = None
kwargs: InitVar[Optional[Dict[str, Any]]] = None
def __post_init__(self, kwargs: Optional[Dict[str, Any]]) -> None:
if kwargs:
for k, v in kwargs.items():
setattr(self, k, v)
self._add_to_asdict(k)
def _add_to_asdict(self, attr:str) -> None:
"""Add an attribute to the list of keys returned by asdict"""
f = field(repr=True)
f.name = attr
f._field_type = _FIELD
getattr(self, _FIELDS)[attr] = f
data = MyDataclass(data1="data_nb_1", kwargs={"test1": 1, "test2": 2})
print(asdict(data))
# {'data1': 'data_nb_1', 'data2': None, 'data3': None, 'test1': 1, 'test2': 2}