如何确定数据类中的字段是否具有默认值或是否已明确设置?

How can I find out whether a field in a dataclass has the default value or whether it's explicitly set?

我有一个 dataclass,我想查明每个字段是否已明确设置,或者它是否由 defaultdefault_factory 填充。

我知道我可以使用 dataclasses.fields(...) 获取所有字段,这可能适用于使用 default 的字段,但不适用于使用 default_factory.[=20= 的字段]

我的最终目标是合并两个数据类实例 AB。虽然 B 应该只覆盖 A 的字段,其中 A 使用默认值。

用例是一个配置对象,可以在多个位置指定,其中一些具有比其他位置更高的优先级。

编辑:一个例子

from dataclasses import dataclass, field

def bar():
  return "bar"

@dataclass
class Configuration:
  foo: str = field(default_factory=bar)

conf1 = Configuration(
)

conf2 = Configuration(
  foo="foo"
)

conf3 = Configuration(
  foo="bar"
)

我想检测到 conf1.foo 正在使用默认值并且 conf2.fooconf3.foo 已明确设置。

首先,根据您对 fields 的了解,您可能会编写类似 merge 函数的内容,实例 z 的示例显示了它的缺点。但是考虑到这个实现完全按照预期的方式使用 dataclass 工具,这意味着它相当稳定,所以如果可能的话,你会想使用这个:

from dataclasses import asdict, dataclass, field, fields, MISSING


@dataclass
class A:
    a: str
    b: float = 5
    c: list = field(default_factory=list)


def merge(base, add_on):
    retain = {}
    for f in fields(base):
        val = getattr(base, f.name)
        if val == f.default:
            continue
        if f.default_factory != MISSING:
            if val == f.default_factory():
                continue
        retain[f.name] = val
    kwargs = {**asdict(add_on), **retain}
    return type(base)(**kwargs)


fill = A('1', 1, [1])

x = A('a')
y = A('a', 2, [3])
z = A('a', 5, [])
print(merge(x, fill))  # good: A(a='a', b=1, c=[1])
print(merge(y, fill))  # good: A(a='a', b=2, c=[3])
print(merge(z, fill))  # bad:  A(a='a', b=1, c=[1])

正确处理 z 案例将涉及 一些 类 hack,我个人只是再次装饰数据类:

from dataclasses import asdict, dataclass, field, fields


def mergeable(inst):
    old_init = inst.__init__

    def new_init(self, *args, **kwargs):
        self.__customs = {f.name for f, _ in zip(fields(self), args)}
        self.__customs |= kwargs.keys()
        old_init(self, *args, **kwargs)

    def merge(self, other):
        retain = {n: v for n, v in asdict(self).items() if n in self.__customs}
        kwargs = {**asdict(other), **retain}
        return type(self)(**kwargs)

    inst.__init__ = new_init
    inst.merge = merge
    return inst


@mergeable
@dataclass
class A:
    a: str
    b: float = 5
    c: list = field(default_factory=list)


fill = A('1', 1, [1])

x = A('a')
y = A('a', 2, [3])
z = A('a', 5, [])

print(x.merge(fill))  # good: A(a='a', b=1, c=[1])
print(y.merge(fill))  # good: A(a='a', b=2, c=[3])
print(z.merge(fill))  # good: A(a='a', b=5, c=[])

虽然这很可能有一些难以猜测的地方 side-effects,因此请您自担风险。