传递 None 时如何将默认值应用于 python 数据类字段?

YeO*_*YeO 15 default-value python-3.x

编辑:为了清楚起见,我需要一个class可以接受许多参数的参数,我知道将提供所有参数,但可能会传递一些参数,因为None在这种情况下我class必须提供默认值。

我想dataclass用一些默认值设置一个简单的,如下所示:

@dataclass
class Specs1:
    a: str
    b: str = 'Bravo'
    c: str = 'Charlie'
Run Code Online (Sandbox Code Playgroud)

我希望能够获得第二个字段的默认值,但仍然为第三个字段设置一个值。我不能用 None 这样做,因为它被愉快地接受为我的字符串的值:

r1 = Specs1('Apple', None, 'Cherry') # Specs1(a='Apple', b=None, c='Cherry')
Run Code Online (Sandbox Code Playgroud)

我想出了以下解决方案:

@dataclass
class Specs2:
    def_b: ClassVar = 'Bravo'
    def_c: ClassVar = 'Charlie'
    a: str
    b: str = def_b
    c: str = def_c

    def __post_init__(self):
        self.b = self.def_b if self.b is None else self.b
        self.c = self.def_c if self.c is None else self.c
Run Code Online (Sandbox Code Playgroud)

这似乎符合预期:

r2 = Specs2('Apple', None, 'Cherry') # Specs2(a='Apple', b='Bravo', c='Cherry')
Run Code Online (Sandbox Code Playgroud)

但是,我觉得它很丑陋,而且我可能在这里遗漏了一些东西。我的实际课程将有更多字段,因此只会变得更丑。

编辑:我应该补充一点,传递给类的参数包含 None 并且我无法控制这方面。

Lar*_*s P 30

简单的解决方案是仅实现默认参数__post_init__()

@dataclass
class Specs2:
    a: str
    b: str
    c: str

    def __post_init__(self):
        if self.b is None:
            self.b = 'Bravo'
        if self.c is None:
            self.c = 'Charlie'
Run Code Online (Sandbox Code Playgroud)

(代码未经测试。如果我有一些细节错误,那不是第一次)

  • 不幸的是,当使用严格的类型注释时,这个解决方案是不可能的。像 mypy 这样的类型检查器不允许从 `Optional[str]` 或 `None` 初始化 `Specs2`。 (9认同)

小智 21

我知道这有点晚了,但受到 MikeSchneeberger 的回答的启发,我对该__post_init__函数做了一个小小的修改,允许您保留标准格式的默认值:

from dataclasses import dataclass, fields
def __post_init__(self):
    # Loop through the fields
    for field in fields(self):
        # If there is a default and the value of the field is none we can assign a value
        if not isinstance(field.default, dataclasses._MISSING_TYPE) and getattr(self, field.name) is None:
            setattr(self, field.name, field.default)
Run Code Online (Sandbox Code Playgroud)

将其添加到数据类中应该确保强制执行默认值,而不需要新的默认类。


Jas*_*n V 12

@dataclass
class Specs1:
    a: str
    b: str = field(default='Bravo')
    c: str = field(default='Charlie')
Run Code Online (Sandbox Code Playgroud)


Mik*_*ger 7

这是另一种解决方案。

定义DefaultValNoneRefersDefault类型:

from dataclasses import dataclass, fields

@dataclass
class DefaultVal:
    val: Any


@dataclass
class NoneRefersDefault:
    def __post_init__(self):
        for field in fields(self):

            # if a field of this data class defines a default value of type
            # `DefaultVal`, then use its value in case the field after 
            # initialization has either not changed or is None.
            if isinstance(field.default, DefaultVal):
                field_val = getattr(self, field.name)
                if isinstance(field_val, DefaultVal) or field_val is None:
                    setattr(self, field.name, field.default.val)
Run Code Online (Sandbox Code Playgroud)

用法:

@dataclass
class Specs3(NoneRefersDefault):
    a: str
    b: str = DefaultVal('Bravo')
    c: str = DefaultVal('Charlie')

r3 = Specs3('Apple', None, 'Cherry')  # Specs3(a='Apple', b='Bravo', c='Cherry')
Run Code Online (Sandbox Code Playgroud)

编辑#1:重写NoneRefersDefault使得以下也是可能的:

@dataclass
r3 = Specs3('Apple', None)  # Specs3(a='Apple', b='Bravo', c='Charlie')
Run Code Online (Sandbox Code Playgroud)

编辑#2:请注意,如果没有类继承自Spec,则最好在数据类中没有默认值并使用“构造函数”函数create_spec

@dataclass
class Specs4:
    a: str
    b: str
    c: str

def create_spec(
        a: str,
        b: str = None,
        c: str = None,
):
    if b is None:
        b = 'Bravo'
    if c is None:
        c = 'Charlie'

    return Spec4(a=a, b=b, c=c)
Run Code Online (Sandbox Code Playgroud)

另见dataclass-abc/example


小智 7

在数据类中,您可以访问类属性的默认值:Specs.b 您可以检查 None 并在需要时传递默认值

代码:

dataclasses.dataclass()
class Specs1:
    a: str
    b: str = 'Bravo'
    c: str = 'Charlie'
a = 'Apple'
b = None
c = 'Potato'
specs = Specs1(a=a, b=b or Specs1.b, c=c or Specs1.c)
Run Code Online (Sandbox Code Playgroud)
>>> specs
Specs1(a='Apple', b='Bravo', c='Potato')
Run Code Online (Sandbox Code Playgroud)

  • 除了具有“dataclass.field(...)”默认值的属性之外,这似乎有效。 (2认同)