【问题标题】:How can I find out whether a field in a dataclass has the default value or whether it's explicitly set?如何确定数据类中的字段是否具有默认值或是否已显式设置?
【发布时间】:2019-06-03 16:01:04
【问题描述】:

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

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

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

【问题讨论】:

  • 你控制类定义吗?你能提供一个minimal reproducible example吗?我猜想如果 Class 中没有机制来捕获超出初始默认分配的属性分配,这是不可能的。
  • 我添加了一个简单的例子。我希望它有所帮助。
  • 是的,我控制着所有的课程。我曾希望有一种内置的方法来做到这一点。我不是 Python 专家,数据类非常优雅且易于使用。

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


【解决方案1】:

首先,鉴于您对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 的情况将涉及一些 类黑客,我个人只是再次装饰数据类:

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

不过,这很可能会产生一些难以猜测的副作用,因此使用风险自负。

【讨论】:

  • btw @LarsFranke,我猜这段代码中的一些表达式有点令人困惑。既然你说你不是专家(还:p),你想在这个答案中加入一些 cmets 吗?
  • 谢谢!他们肯定是,但我想我在做一些研究后理解了正在发生的事情的要点。感谢您投入时间。为了解决这个问题,我学到了很多关于 Python 的知识。关于你的z 用例,有一个论点是你原来的用例实际上是 good 而不是坏的。因为当我决定明确提供一个空列表时,这可能是我的意图。我了解到没有办法(我知道)将“默认”值与手动设置的值区分开来。我认为这发生在非常低​​的水平。
  • 它发生的层次实际上并没有那么深。 python 通过 dunder 方法向用户公开了相当多的它自己的实现。对于这种情况,另一种解决问题的方法是“我的对象的参数何时以及如何准确设置?”本来可以破解数据类的__setitem__ 函数,该函数用于在构造函数中将foo 分配给self.foo
  • 我的意思是(我明白这将是一个完全独立的问题)是__setitem__ 将被调用一个值。但我不知道这个值是从哪里来的,还是我?我假设 Python 解析函数调用的机制(我理解数据类归结为 -> init 使用默认值)查看实际的函数调用,然后用默认值填充空白。 __setitem__ 不知道是“Python”还是设置这些值的用户,对吧?
  • 哎呀,我的意思是__setattr__ 不是__setitem__。我认为你是对的,这种方法可能根本行不通。现在有办法知道值的来源,所以像我在第二个实现中所做的那样检查构造函数调用参数可能是完成它的唯一方法。
猜你喜欢
  • 2019-05-04
  • 2017-05-11
  • 1970-01-01
  • 2011-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-24
相关资源
最近更新 更多