如何确定数据类中的字段是否具有默认值或是否已明确设置?
How can I find out whether a field in a dataclass has the default value or whether it's explicitly set?
我有一个 dataclass
,我想查明每个字段是否已明确设置,或者它是否由 default
或 default_factory
填充。
我知道我可以使用 dataclasses.fields(...)
获取所有字段,这可能适用于使用 default
的字段,但不适用于使用 default_factory
.[=20= 的字段]
我的最终目标是合并两个数据类实例 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
已明确设置。
首先,根据您对 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,因此请您自担风险。
我有一个 dataclass
,我想查明每个字段是否已明确设置,或者它是否由 default
或 default_factory
填充。
我知道我可以使用 dataclasses.fields(...)
获取所有字段,这可能适用于使用 default
的字段,但不适用于使用 default_factory
.[=20= 的字段]
我的最终目标是合并两个数据类实例 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
已明确设置。
首先,根据您对 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,因此请您自担风险。