具有可选属性的 python 数据类

Bac*_*ics 43 python python-dataclasses

如何制作数据类的可选属性?

from dataclasses import dataclass

@dataclass
class CampingEquipment:
    knife: bool
    fork: bool
    missing_flask_size: # what to write here?
    
kennys_stuff = {
    'knife': True,
    'fork': True
}

print(CampingEquipment(**kennys_stuff))
Run Code Online (Sandbox Code Playgroud)

我尝试过field(init=False),但它给了我:

TypeError: CampingEquipment.__init__() missing 1 required positional argument: 'missing_flask_size'
Run Code Online (Sandbox Code Playgroud)

我所说的“可选”是指__dict__可能包含或不包含“missing_flask_size”键。如果我设置默认值,那么密钥将在那里,但在某些情况下不应该在那里。我想检查它的类型是否存在。

我尝试将 移动field(init=False)到类型位置(冒号之后),这样我就可以更明确地说明我想要的可选内容是键而不是值。

所以我希望这个测试能够通过:

with pytest.raises(AttributeError):
    ce = CampingEquipment(**kennys_stuff)
    print(ce.missing_flask_size)
Run Code Online (Sandbox Code Playgroud)

Tre*_*ner 59

不可能使用数据类来创建有时存在有时不存在的属性,因为生成的__init____eq____repr__等对它们检查的属性进行了硬编码。

但是,可以创建一个带有可选参数的数据类,该参数使用属性的默认值(当未提供该属性时)。

from dataclasses import dataclass
from typing import Optional

@dataclass
class CampingEquipment:
    knife: bool
    fork: bool
    missing_flask_size: Optional[int] = None
    
kennys_stuff = {
    'knife':True,
    'fork': True
}

print(CampingEquipment(**kennys_stuff))
Run Code Online (Sandbox Code Playgroud)

并且可以使用被接受__init__但不是实际字段的参数来创建数据类。所以你可以这样做:

from dataclasses import dataclass, InitVar
from typing import Optional

@dataclass
class CampingEquipment:
    knife: bool
    fork: bool
    missing_flask_size: InitVar[Optional[int]] = None

    def __post_init__(self, missing_flask_size):
        if missing_flask_size is not None:
            self.missing_flask_size = missing_flask_size
Run Code Online (Sandbox Code Playgroud)

如果您确实希望类要么具有该属性,要么根本不具有该属性,您可以对数据类进行子类化,并创建一个工厂函数,根据该missing_flask_size属性是否存在来创建一个类或另一个类:

from dataclasses import dataclass

@dataclass
class CampingEquipment:
    knife: bool
    fork: bool


@dataclass
class CampingEquipmentWithFlask:
    missing_flask_size: int


def equipment(**fields):
    if 'missing_flask_size' in fields:
        return CampingEquipmentWithFlask(**fields)
    return CampingEquipment(**fields)
    

kennys_stuff = {
    'knife':True,
    'fork': True
}

print(CampingEquipment(**kennys_stuff))
Run Code Online (Sandbox Code Playgroud)

如果您真的想要(但我不推荐),您甚至可以自定义 of__new__以在给出CampingEquipment该参数时返回该特殊子类的实例(尽管您还需要在那个班级)。missing_flask_sizeinit=False__init__


use*_*ica 12

对象field应该与 一起使用=,就像默认值一样,而不是:像注释一样。

指定init=False字段意味着调用者根本无法为其传递值。init=False字段应该设置在 中__post_init__如下所示

@dataclass
class Example:
    a: int
    b: int
    c: int = field(init=False)

    def __post_init__(self):
        self.c = self.a + self.b

print(Example(1, 2).c) # prints 3
Run Code Online (Sandbox Code Playgroud)

如果您想让调用者可以选择提供值,则可以设置默认值。如果调用者未提供值,则将使用默认值

@dataclass
class Example:
    a: int
    b: int
    c: int = -1

print(Example(1, 2).c) # prints -1
print(Example(1, 2, 3).c) # prints 3
Run Code Online (Sandbox Code Playgroud)

  • 我还要提到“field(default=...)”,对于可变类型,“field(default_factory=...)”,另外,因为不清楚OP中最后一个字段的类型是什么。 (2认同)