我有一个dataclass
,我想了解是否明确设置了每个字段,还是由default
或default_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.foo
和conf3.foo
的设置是明确的。
答案 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=[])
尽管这很可能会产生一些难以猜测的副作用,所以使用后果自负。