当您使用Python继承创建数据类时,不能保证所有具有默认值的字段将出现在所有没有默认值的字段之后。
一个简单的解决方案是避免使用多重继承来构造“合并”数据类。相反,我们可以通过对父数据类的字段进行过滤和排序来构建合并的数据类。
试试这个merge_dataclasses()函数:
import dataclasses
import functools
from typing import Iterable, Type
def merge_dataclasses(
cls_name: str,
*,
merge_from: Iterable[Type],
**kwargs,
):
"""
Construct a dataclass by merging the fields
from an arbitrary number of dataclasses.
Args:
cls_name: The name of the constructed dataclass.
merge_from: An iterable of dataclasses
whose fields should be merged.
**kwargs: Keyword arguments are passed to
:py:func:`dataclasses.make_dataclass`.
Returns:
Returns a new dataclass
"""
# Merge the fields from the dataclasses,
# with field names from later dataclasses overwriting
# any conflicting predecessor field names.
each_base_fields = [d.__dataclass_fields__ for d in merge_from]
merged_fields = functools.reduce(
lambda x, y: {**x, **y}, each_base_fields
)
# We have to reorder all of the fields from all of the dataclasses
# so that *all* of the fields without defaults appear
# in the merged dataclass *before* all of the fields with defaults.
fields_without_defaults = [
(f.name, f.type, f)
for f in merged_fields.values()
if isinstance(f.default, dataclasses._MISSING_TYPE)
]
fields_with_defaults = [
(f.name, f.type, f)
for f in merged_fields.values()
if not isinstance(f.default, dataclasses._MISSING_TYPE)
]
fields = [*fields_without_defaults, *fields_with_defaults]
return dataclasses.make_dataclass(
cls_name=cls_name,
fields=fields,
**kwargs,
)
然后,您可以按照如下方式合并数据类。注意,我们可以合并A和B,默认字段B和d被移动到合并的数据类的末尾。
@dataclasses.dataclass
class A:
a: int
b: int = 0
@dataclasses.dataclass
class B:
c: int
d: int = 0
C = merge_dataclasses(
"C",
merge_from=[A, B],
)
# Note that
print(C(a=1, d=1).__dict__)
# {'a': 1, 'd': 1, 'b': 0, 'c': 0}
当然,这种解决方案的缺陷是C实际上不继承A和B,这意味着您不能使用isinstance()或其他类型断言来验证C的亲本。