dataclasses ---数据类

源代码: Lib/dataclasses.py


此模块提供一个装饰器和自动添加生成的 special method 例如 __init__()__repr__() 到用户定义的类。最初的描述是 PEP 557 .

在这些生成的方法中使用的成员变量是使用 PEP 526 键入批注。例如,此代码:

from dataclasses import dataclass

@dataclass
class InventoryItem:
    """Class for keeping track of an item in inventory."""
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

除其他外,还将添加 __init__() 看起来像是:

def __init__(self, name: str, unit_price: float, quantity_on_hand: int=0):
    self.name = name
    self.unit_price = unit_price
    self.quantity_on_hand = quantity_on_hand

注意,这个方法是自动添加到类中的:它不是在 InventoryItem 定义如上所示。

3.7 新版功能.

模块级修饰器、类和函数

@dataclasses.dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)

此函数是 decorator 用于添加生成的 special method s到类,如下所述。

这个 dataclass() decorator检查类以查找 field 美国 field 定义为具有 type annotation . 除了下面描述的两个例外,没有 dataclass() 检查变量批注中指定的类型。

所有生成的方法中字段的顺序是它们在类定义中出现的顺序。

这个 dataclass() decorator将向类添加各种“dunder”方法,如下所述。如果类中已经存在任何添加的方法,则行为取决于参数,如下所述。decorator返回调用的同一个类;不创建新类。

如果 dataclass() 就像一个没有参数的简单装饰器一样使用,它的作用就像在这个签名中记录了默认值一样。也就是说,这三种用途 dataclass() 相当于:

@dataclass
class C:
    ...

@dataclass()
class C:
    ...

@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
class C:
   ...

参数 dataclass() 是:

  • init :如果为真(默认值),则为 __init__() 将生成方法。

    如果类已经定义 __init__() ,此参数被忽略。

  • repr :如果为真(默认值),则为 __repr__() 将生成方法。生成的repr字符串将具有类名和每个字段的名称和repr,其顺序在类中定义。标记为从报告中排除的字段不包括在内。例如: InventoryItem(name='widget', unit_price=3.0, quantity_on_hand=10) .

    如果类已经定义 __repr__() ,此参数被忽略。

  • eq :如果为真(默认值),则 __eq__() 将生成方法。此方法按顺序将类作为其字段的元组进行比较。比较中的两个实例的类型必须相同。

    如果类已经定义 __eq__() ,此参数被忽略。

  • order :如果为真(默认为 False__lt__()__le__()__gt__()__ge__() 将生成方法。它们按照顺序将类与字段的元组进行比较。比较中的两个实例的类型必须相同。如果 order 是真的 eq 是假的,A ValueError 提高了。

    如果类已经定义了 __lt__()__le__()__gt__()__ge__() 然后 TypeError 提高了。

  • unsafe_hash 如果 False (默认),a __hash__() 方法是根据 eqfrozen 被设置。

    __hash__() 是内置的 hash() 以及将对象添加到hash集合(如字典和集合)时。有一个 __hash__() 表示类的实例是不可变的。易变性是一个复杂的属性,它取决于程序员的意图、存在和行为 __eq__() 以及 eqfrozen 中的flag dataclass() decorator。

    默认情况下, dataclass() 不会隐式添加 __hash__() 方法,除非这样做是安全的。它也不会添加或更改显式定义的现有 __hash__() 方法。设置类属性 __hash__ = None 对python有特定的意义,如中所述 __hash__() 文档。

    如果 __hash__() 未显式定义,或者如果将其设置为 None 然后 dataclass() may 添加隐式 __hash__() 方法。虽然不推荐,但您可以强制 dataclass() 创建一个 __hash__() 方法与 unsafe_hash=True . 如果您的类在逻辑上是不可变的,但仍然可以发生变化,那么就可能发生这种情况。这是一个专门的用例,应该仔细考虑。

    以下是管理隐式创建 __hash__() 方法。请注意,不能同时使用 __hash__() 数据类和集合中的方法 unsafe_hash=True ;这将导致 TypeError .

    如果 eqfrozen 默认情况下都是真的 dataclass() 将生成一个 __hash__() 方法。如果 eq 是真的 frozen __hash__() 将被设置为 None ,将其标记为不可清洗(因为它是可变的)。如果 eq __hash__() 将保持原样,意味着 __hash__() 将使用超类的方法(如果超类是 object ,这意味着它将返回到基于ID的散列)。

  • frozen :如果为真(默认为 False ),分配给字段将生成异常。这将模拟只读冻结实例。如果 __setattr__()__delattr__() 在类中定义,然后 TypeError 被提升。请参阅下面的讨论。

field s可以选择使用标准的python语法指定默认值:

@dataclass
class C:
    a: int       # 'a' has no default value
    b: int = 0   # assign a default value for 'b'

在这个例子中,两者都是 ab 将包括在添加的 __init__() 方法,其定义为:

def __init__(self, a: int, b: int = 0):

TypeError 如果没有默认值的字段跟在具有默认值的字段后面,则将引发。无论是在单个类中发生这种情况,还是由于类继承,这都是正确的。

dataclasses.field(*, default=MISSING, default_factory=MISSING, repr=True, hash=None, init=True, compare=True, metadata=None)

对于常见和简单的用例,不需要其他功能。但是,有些数据类功能需要每个字段附加信息。为了满足对附加信息的需求,可以使用对提供的 field() 功能。例如::

@dataclass
class C:
    mylist: list[int] = field(default_factory=list)

c = C()
c.mylist += [1, 2, 3]

如上图所示, MISSING 值是用于检测 defaultdefault_factory 提供参数。使用这个哨兵是因为 None 是的有效值 default .任何代码都不应直接使用 MISSING 价值。

参数 field() 是:

  • default :如果提供,这将是此字段的默认值。这是需要的,因为 field() 调用本身替换默认值的正常位置。

  • default_factory :如果提供,则必须是可调用的零参数,当此字段需要默认值时将调用该参数。除其他用途外,它还可以用于指定具有可变默认值的字段,如下所述。指定两者都是错误的 defaultdefault_factory .

  • init :如果为真(默认值),则此字段将作为生成的 __init__() 方法。

  • repr :如果为真(默认值),则此字段包含在由生成的 __repr__() 方法。

  • compare :如果为真(默认值),则此字段包含在生成的相等和比较方法中。 (__eq__()__gt__() 等。

  • hash :这可能是一个bool或 None . 如果为真,则此字段包含在生成的 __hash__() 方法。如果 None (默认值),使用 compare :这通常是预期的行为。如果用于比较,则应在hash中考虑字段。将此值设置为除 None 不鼓励。

    设置的一个可能原因 hash=False 但是 compare=True 如果一个字段的hash值计算起来很昂贵,那么该字段就需要进行相等性测试,并且还有其他字段对该类型的hash值起作用。即使字段从hash中排除,它仍将用于比较。

  • metadata :这可以是映射,也可以是无映射。无被视为空dict。此值被封装在 MappingProxyType() 使其只读,并在 Field 对象。数据类根本不使用它,它作为第三方扩展机制提供。多个第三方可以各自拥有自己的密钥,用作元数据中的命名空间。

如果字段的默认值是通过调用 field() ,则该字段的class属性将替换为指定的 default 价值。如果没有 default 提供,则类属性将被删除。其目的是在 dataclass() decorator运行时,类属性将全部包含字段的默认值,就像指定了默认值本身一样。例如,之后::

@dataclass
class C:
    x: int
    y: int = field(repr=False)
    z: int = field(repr=False, default=10)
    t: int = 20

类属性 C.z10 ,类属性 C.t20 以及类属性 C.xC.y 不会被设置。

class dataclasses.Field

Field 对象描述每个定义的字段。这些对象是在内部创建的,并由 fields() 模块级方法(见下文)。用户不应实例化 Field 直接对象。其文件化属性包括:

  • name :字段的名称。

  • type :字段的类型。

  • defaultdefault_factoryinitreprhashcomparemetadata 具有相同的含义和价值 field() 宣言。

其他属性可能存在,但它们是私有的,不能检查或依赖。

dataclasses.fields(class_or_instance)

返回的元组 Field 定义此数据类的字段的对象。接受数据类或数据类的实例。引发 TypeError 如果没有传递一个数据类或实例。不返回以下伪字段: ClassVarInitVar .

dataclasses.asdict(instance, *, dict_factory=dict)

转换数据类 instance 到dict(通过使用factory函数 dict_factory )每个数据类都转换为其字段的dict,如 name: value 对。数据类、dict、list和tuples递归到中。例如::

@dataclass
class Point:
     x: int
     y: int

@dataclass
class C:
     mylist: list[Point]

p = Point(10, 20)
assert asdict(p) == {'x': 10, 'y': 20}

c = C([Point(0, 0), Point(10, 4)])
assert asdict(c) == {'mylist': [{'x': 0, 'y': 0}, {'x': 10, 'y': 4}]}

引发 TypeError 如果 instance 不是DataClass实例。

dataclasses.astuple(instance, *, tuple_factory=tuple)

转换数据类 instance 到元组(通过使用factory函数 tuple_factory )每个数据类都转换为其字段值的元组。数据类、dict、list和tuples递归到中。

从上一个示例继续:

assert astuple(p) == (10, 20)
assert astuple(c) == ([(0, 0), (10, 4)],)

引发 TypeError 如果 instance 不是DataClass实例。

dataclasses.make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)

创建名为的新数据类 cls_name ,字段定义见 fields ,中给出的基类 bases ,并使用中给定的命名空间初始化 namespace . fields 是一个iterable,其元素分别为 name(name, type)(name, type, Field) . 如果只是 name 提供, typing.Any 用于 type . 价值观 initrepreqorderunsafe_hashfrozen 与它们在中的含义相同 dataclass() .

此函数不是严格要求的,因为任何用于创建新类的python机制 __annotations__ 然后可以应用 dataclass() 函数将该类转换为数据类。此功能是为了方便起见而提供的。例如::

C = make_dataclass('C',
                   [('x', int),
                     'y',
                    ('z', int, field(default=5))],
                   namespace={'add_one': lambda self: self.x + 1})

等于:

@dataclass
class C:
    x: int
    y: 'typing.Any'
    z: int = 5

    def add_one(self):
        return self.x + 1
dataclasses.replace(instance, /, **changes)

创建相同类型的新对象 instance ,将字段替换为中的值 changes . 如果 instance 不是数据类,引发 TypeError . 如果值在 changes 不指定字段,引发 TypeError .

新返回的对象是通过调用 __init__() 数据类的方法。这确保了 __post_init__() 如果存在,也被称为。

只有不带默认值的init变量(如果存在)必须在调用时指定 replace() 以便将它们传递给 __init__()__post_init__() .

这是一个错误 changes 包含定义为具有 init=False . 一 ValueError 在这种情况下将被引发。

预先警告如何 init=False 调用期间字段工作 replace() . 它们不是从源对象复制的,而是在中初始化的 __post_init__() ,如果它们已经初始化。预计 init=False 字段将很少被明智地使用。如果使用它们,那么最好使用备用类构造函数,或者自定义 replace() (或类似名称)处理实例复制的方法。

dataclasses.is_dataclass(class_or_instance)

返回 True 如果其参数是数据类或其实例,则返回 False .

如果需要知道某个类是否是数据类的实例(而不是数据类本身),请添加对 not isinstance(obj, type) ::

def is_dataclass_instance(obj):
    return is_dataclass(obj) and not isinstance(obj, type)

后初始化处理

生成的 __init__() 代码将调用名为 __post_init__() 如果 __post_init__() 在类上定义。它通常被称为 self.__post_init__() . 但是,如果有的话 InitVar 字段已定义,它们也将传递给 __post_init__() 按照在类中定义它们的顺序。如果没有 __init__() 生成方法,然后 __post_init__() 不会自动调用。

在其他用途中,这允许初始化依赖于一个或多个其他字段的字段值。例如::

@dataclass
class C:
    a: float
    b: float
    c: float = field(init=False)

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

有关将参数传递给 __post_init__() . 另请参见有关如何 replace() 句柄 init=False 领域。

类变量

两个地方之一 dataclass() 实际检查字段的类型是确定字段是否是中定义的类变量 PEP 526 . 它通过检查字段类型是否为 typing.ClassVar .如果字段是 ClassVar 将其作为字段排除在考虑范围之外,并被数据类机制忽略。这样 ClassVar 模块级不返回伪字段 fields() 功能。

仅初始化变量

另一个地方 dataclass() 检查类型批注是为了确定字段是否是仅初始化的变量。它通过查看字段的类型是否为 dataclasses.InitVar . 如果字段是 InitVar ,它被认为是一个伪字段,称为仅初始化字段。因为它不是一个真字段,所以模块级不会返回它。 fields() 功能。仅init字段作为参数添加到生成的 __init__() 方法,并传递给可选的 __post_init__() 方法。数据类不使用它们。

例如,假设一个字段将从数据库初始化,如果在创建类时没有提供值::

@dataclass
class C:
    i: int
    j: int = None
    database: InitVar[DatabaseType] = None

    def __post_init__(self, database):
        if self.j is None and database is not None:
            self.j = database.lookup('j')

c = C(10, database=my_database)

在这种情况下, fields() 将返回 Field 对象为 ij ,但不是为了 database .

冻结的实例

无法创建真正不可变的Python对象。但是,路过 frozen=Truedataclass() decorator可以模拟不可变性。在这种情况下,数据类将添加 __setattr__()__delattr__() 类的方法。这些方法将提高 FrozenInstanceError 当被调用时。

使用时会有很小的性能损失 frozen=True__init__() 不能使用简单赋值来初始化字段,并且必须使用 object.__setattr__() .

遗传

当数据类由 dataclass() decorator,它在reverse mro(也就是从 object )并且,对于它找到的每个数据类,将该基类中的字段添加到字段的有序映射中。在添加所有的基类字段之后,它会将自己的字段添加到有序映射中。所有生成的方法都将使用字段的组合、计算顺序映射。因为字段是按插入顺序排列的,所以派生类会覆盖基类。一个例子:

@dataclass
class Base:
    x: Any = 15.0
    y: int = 0

@dataclass
class C(Base):
    z: int = 10
    x: int = 15

最后的字段列表是, xyz . 最后一种类型 xint ,如类中所述 C .

生成的 __init__() 方法 C 看起来像:

def __init__(self, x: int = 15, y: int = 0, z: int = 10):

默认工厂功能

如果A field() 指定一个 default_factory ,当需要字段的默认值时,使用零参数调用它。例如,要创建列表的新实例,请使用:

mylist: list = field(default_factory=list)

如果字段被排除在 __init__() (使用) init=False )字段还指定 default_factory ,则将始终从生成的 __init__() 功能。这是因为没有其他方法可以给字段一个初始值。

可变默认值

python在类属性中存储默认的成员变量值。考虑这个例子,不使用数据类:

class C:
    x = []
    def add(self, element):
        self.x.append(element)

o1 = C()
o2 = C()
o1.add(1)
o2.add(2)
assert o1.x == [1, 2]
assert o1.x is o2.x

注意类的两个实例 C 共享同一类变量 x ,正如预期的那样。

使用数据类, if 此代码有效:

@dataclass
class D:
    x: List = []
    def add(self, element):
        self.x += element

它将生成类似以下内容的代码:

class D:
    x = []
    def __init__(self, x=x):
        self.x = x
    def add(self, element):
        self.x += element

assert D().x is D().x

这与使用类的原始示例具有相同的问题 C . 也就是说,类的两个实例 D 不指定值的 x 创建类实例时,将共享 x . 因为数据类只使用普通的python类创建,所以它们也共享这种行为。数据类没有通用的方法来检测这种情况。相反,数据类将引发 TypeError 如果它检测到类型的默认参数 listdictset . 这是一个局部解决方案,但它确实可以防止许多常见错误。

使用默认factory函数可以创建可变类型的新实例作为字段的默认值:

@dataclass
class D:
    x: list = field(default_factory=list)

assert D().x is not D().x

例外情况

exception dataclasses.FrozenInstanceError

当隐式定义 __setattr__()__delattr__() 在用定义的数据类上调用 frozen=True .