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

时间:2019-06-03 16:01:04

标签: python python-3.x python-dataclasses

我有一个dataclass,我想了解是否明确设置了每个字段,还是由defaultdefault_factory填充了它。

我知道我可以使用dataclasses.fields(...)来获取所有字段,并且这可能适用于使用default的字段,但不适用于使用default_factory的字段。

我的最终目标是合并两个数据类实例 A B 。虽然 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的设置是明确的。

1 个答案:

答案 0 :(得分:1)

首先,在了解merge的情况下,您可能会写类似fields的函数,并以实例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案件将涉及某种黑客,我个人将再次修饰数据类:

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=[])

尽管这很可能会产生一些难以猜测的副作用,所以使用后果自负。