dataclasses
--- 数据类¶
这个模块提供了一个装饰器和一些函数,用于自动为用户自定义的类添加生成的 特殊方法 例如 __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
定义中直接指定的。
Added in version 3.7.
模块内容¶
- @dataclasses.dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False)¶
此函数是一个 decorator,它被用于将生成的 特殊方法 添加到类中,如下所述。
@dataclass
装饰器会检查类以找到其中的field
。field
被定义为具有 类型标注 的类变量。 除了下面所述的两个例外,在@dataclass
中没有任何东西会去检查变量标注中指定的类型。这些字段在所有生成的方法中的顺序,都是它们在类定义中出现的顺序。
@dataclass
装饰器将把各种“双下线”方法添加到类,具体如下所述。 如果所添加的任何方法在类中已存在,其行为将取决于形参的值,具体如下所述。 该装饰器将返回执行其调用的类而不会创建新类。如果
@dataclass
仅被用作不带形参的简单装饰器,其行为相当于使用在此签名中记录的默认值。 也就是说,这三种@dataclass
的用法是等价的:@dataclass class C: ... @dataclass() class C: ... @dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False) class C: ...
@dataclass
的形参有:init: 如为真值(默认),将生成
__init__()
方法。如果类已经定义了
__init__()
,此形参将被忽略。repr: 如为真值(默认),将生成
__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 为假值,则会引发ValueError
。如果类已经定义了
__lt__()
,__le__()
,__gt__()
或者__ge__()
中的任意一个,将引发TypeError
。unsafe_hash: 如为真值,则强制
dataclasses
创建__hash__()
方法,即使这样做可能是不安全的。 在其他情况下,将根据 eq 和 frozen 的设置方式来生成__hash__()
方法。 默认值为False
。__hash__()
会在对象被添加到哈希多项集如字典和集合时由内置的hash()
使用。 具有__hash__()
就意味着类的实例是不可变的。 可变性是一个依赖于程序员的实际意图、__eq__()
是否存在及其具体行为,以及@dataclass
装饰器中 eq 和 frozen 旗标的值的复杂特征属性。在默认情况下,
@dataclass
不会隐式地添加__hash__()
方法,除非这样做是安全的。 它也没会添加或更改现有的显式定义的__hash__()
方法。 设置类属性__hash__ = None
对 Python 具有特定含义,如__hash__()
文档中所述。如果
__hash__()
没有被显式定义,或者它被设为None
,则@dataclass
可能 会添加一个隐式__hash__()
方法。 虽然并不推荐,但你可以用unsafe_hash=True
来强制让@dataclass
创建一个__hash__()
方法。 如果你的类在逻辑上不可变但却仍然可被修改那么可能就是这种情况一。 这是一个特殊用例并且应当被小心地处理。以下是针对隐式创建
__hash__()
方法的规则。 请注意你的数据类中不能既有显式的__hash__()
方法又设置unsafe_hash=True
;这将导致TypeError
。如果 eq 和 frozen 均为真值,则默认
@dataclass
将为你生成__hash__()
方法。 如果 eq 为真值而 frozen 为假值,则__hash__()
将被设为None
,即将其标记为不可哈希(因为它属于可变对象)。 如果 eq 为假值,则__hash__()
将保持不变,这意味着将使用超类的__hash__()
方法(如果超类是object
,这意味着它将回退为基于 id 的哈希)。frozen: 如为超值 (默认为
False
),则对字段赋值将引发异常。 这模拟了只读的冻结实例。 详见下方的 讨论。如果类中定义了
__setattr__()
或__delattr__()
方法,并且 frozen 参数为 True,则会引发TypeError
异常。match_args: 如为真值 (默认为
True
),则将根据传给生成的__init__()
方法的非关键字形参列表来创建 the__match_args__
元组(即使没有生成__init__()
,见上文)。 如为假值,或者如果__match_args__
已在类中定义,则不会生成__match_args__
。
Added in version 3.10.
kw_only: 如为真值 (默认值为
False
),则所有字段都将被标记为仅限关键字的。 如果一个字段被标记为仅限关键字的,则唯一的影响是由仅限关键字的字段生成的__init__()
形参在__init__()
被调用时必须以关键字形式来指定。 详情参见 parameter 术语表条目。 另请参阅KW_ONLY
一节。仅限关键字字段不会被包括在
__match_args__
中。
Added in version 3.10.
警告
在使用
slots=True
时向基类__init_subclass__()
传入形参将导致TypeError
。 应使用不带参数的__init_subclass__
或使用默认值的绕过方式。 请参阅 gh-91126 了解完整细节。Added in version 3.10.
weakref_slot: 如为真值 (默认为
False
),则添加一个名为 "__weakref__" 的槽位,这是使得一个实例可以弱引用
所必需的。 指定weakref_slot=True
而不同时指定slots=True
将会导致错误。
Added in version 3.11.
可以用普通的 Python 语法为各个
field
指定默认值:@dataclass class C: a: int # 'a' 没有默认值 b: int = 0 # 为 'b' 赋默认值
在这个例子中,
a
和b
都将被包括在所添加的__init__()
方法中,该方法将被定义为:def __init__(self, a: int, b: int = 0):
如果在具有默认值的字段之后存在没有默认值的字段,将会引发
TypeError
。无论此情况是发生在单个类中还是作为类继承的结果,都是如此。
- dataclasses.field(*, default=MISSING, default_factory=MISSING, init=True, repr=True, hash=None, compare=True, metadata=None, kw_only=MISSING, doc=None)¶
对于常见和简单的用例,不需要其他的功能。 但是,有些数据类的特性需要额外的每字段信息。 为了满足这种对额外信息的需求,你可以通过调用所提供的
field()
函数来替换默认的字段值。 例如:@dataclass class C: mylist: list[int] = field(default_factory=list) c = C() c.mylist += [1, 2, 3]
如上所示,
MISSING
值是一个哨兵对象,用于检测一些形参是否由用户提供。使用它是因为None
对于一些形参来说是有效的用户值。任何代码都不应该直接使用MISSING
值。传给
field()
的形参有:default: 如果提供,这将为该字段的默认值。 设置此形参是因为
field()
调用本身会替换通常的默认值所在位置。default_factory: 如果提供,它必须是一个零参数的可调用对象,它将在该字段需要一个默认值时被调用。 在其他目的以外,它还可被用于指定具有可变默认值的字段,如下所述。 同时指定 default 和 default_factory 将会导致错误。
init: 如为真值(默认),则该字段将作为一个形参被包括在生成的
__init__()
方法中。repr: 如为真值(默认值),则该字段将被包括在生成的
__repr__()
方法所返回的字符串中。hash: 这可以是一个布尔值或
None
。 如为真值,则此字段将被包括在所生成的__hash__()
方法中。 如为假值,则此字段将被排除在所生成的__hash__()
之外。 如为None
(默认值),则使用 compare 的值:这通常是预期的行为,因为一个字段如果被用于比较那么就应当被包括在哈希运算中。 不建议将该值设为None
以外的任何值。设置
hash=False
但compare=True
的一个合理情况是,一个计算哈希值的代价很高的字段是检验等价性需要的,且还有其他字段可以用于计算类型的哈希值。可以从哈希值中排除该字段,但仍令它用于比较。compare: 如为真值(默认),则该字段将被包括在生成的相等和比较方法中 (
__eq__()
,__gt__()
等等)。metadata: 这可以是一个映射或为
None
。None
将被当作空字典来处理。 这个值将被包装在MappingProxyType()
以便其为只读,并暴露在Field
对象上。 它完全不被数据类所使用,并且是作为第三方扩展机制提供的。 多个第三方可以各自拥有其本身的键,以用作元数据的命名空间。kw_only: 如为真值,则该字段将被标记为仅限关键字的。 这将在计算所生成的
__init__()
方法的形参时被使用。仅限关键字字段也不会被包括在
__match_args__
中。
Added in version 3.10.
doc: 针对该字段的可选的文档字符串。
Added in version 3.14.
If the default value of a field is specified by a call to
field()
, then the class attribute for this field will be replaced by the specified default value. If default is not provided, then the class attribute will be deleted. The intent is that after the@dataclass
decorator runs, the class attributes will all contain the default values for the fields, just as if the default value itself were specified. For example, after:@dataclass class C: x: int y: int = field(repr=False) z: int = field(repr=False, default=10) t: int = 20
类属性
C.z
将为10
,类属性C.t
将为20
,类属性C.x
和C.y
将不被设置。
- class dataclasses.Field¶
Field
对象描述每个已定义的字段。 这些对象是在内部创建的,并会由fields()
模块块方法返回(见下文)。 用户绝不应直接实例化Field
对象。 已写入文档的属性如下:name
: 字段的名称。type
: 字段的类型。default
,default_factory
,init
,repr
,hash
,compare
,metadata
和kw_only
具有与field()
函数中对应参数相同的含义和值。
可能存在其他属性,但它们是私有的。用户不应检查或依赖于这些属性。
- class dataclasses.InitVar¶
InitVar[T]
类型标注用于描述 仅限初始化 变量。 使用InitVar
标注的字段将被视作伪字段,因此既不会被fields()
函数返回也不会在除了作为传给__init__()
的和可选的__post_init__()
的形参添加之外以任何方式被使用。
- dataclasses.fields(class_or_instance)¶
返回一个能描述此数据类所包含的字段的元组,元组的每一项都是
Field
对象。接受数据类或数据类的实例。如果没有传递一个数据类或实例将引发TypeError
。不返回ClassVar
或InitVar
等伪字段。
- dataclasses.asdict(obj, *, dict_factory=dict)¶
将数据类 obj 转换为一个字典 (使用工厂函数 dict_factory)。 每个数据类会被转换为以
name: value
键值对来存储其字段的字典。 数据类、字典、列表和元组会被递归地处理。 其他对象会通过copy.deepcopy()
来拷贝。在嵌套的数据类上使用
asdict()
的例子:@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}]}
要创建一个浅拷贝,可以使用以下的变通方法:
{field.name: getattr(obj, field.name) for field in fields(obj)}
如果 obj 不是一个数据类实例则
asdict()
将引发TypeError
。
- dataclasses.astuple(obj, *, tuple_factory=tuple)¶
将数据类 obj 转换为元组 (使用工厂函数 tuple_factory)。 每个数据类将被转换为由其字段值组成的元组。 数据类、字典、列表和元组会被递归地处理。 其他对象会通过
copy.deepcopy()
来拷贝。继续前一个例子:
assert astuple(p) == (10, 20) assert astuple(c) == ([(0, 0), (10, 4)],)
要创建一个浅拷贝,可以使用以下的变通方法:
tuple(getattr(obj, field.name) for field in dataclasses.fields(obj))
如果 obj 不是一个数据类实例则
astuple()
将引发TypeError
。
- dataclasses.make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False, module=None, decorator=dataclass)¶
Creates a new dataclass with name cls_name, fields as defined in fields, base classes as given in bases, and initialized with a namespace as given in namespace. fields is an iterable whose elements are each either
name
,(name, type)
, or(name, type, Field)
. If justname
is supplied,typing.Any
is used fortype
. The values of init, repr, eq, order, unsafe_hash, frozen, match_args, kw_only, slots, and weakref_slot have the same meaning as they do in@dataclass
.如果定义了 module,则该数据类的
__module__
属性将被设为该值。 在默认情况下,它将被设为调用方的模块名。The decorator parameter is a callable that will be used to create the dataclass. It should take the class object as a first argument and the same keyword arguments as
@dataclass
. By default, the@dataclass
function is used.This function is not strictly required, because any Python mechanism for creating a new class with
__annotations__
can then apply the@dataclass
function to convert that class to a dataclass. This function is provided as a convenience. For example: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
Added in version 3.14: 增加了 decorator 形参。
- dataclasses.replace(obj, /, **changes)¶
创建一个与 obj 类型相同的新对象,将字段替换为 changes 的值。 如果 obj 不是数据类,则会引发
TypeError
。 如果 changes 中的键不是给定数据类的字段名,则会引发TypeError
。新返回的对象是通过调用数据类的
__init__()
方法来创建的。 这确保了如果存在__post_init__()
,则它也会被调用。如果存在任何没有默认值的仅初始化变量,那么必须在调用
replace()
时指定它们的值,以便它们可以被传递给__init__()
和__post_init__()
。如果 changes 包含被任何定义为 defined as having
init=False
的字段都会导致错误。 在此情况下将引发ValueError
。需要预先注意
init=False
字段在对replace()
的调用期间的行为。 如果它们会被初始化,它们就不会从源对象拷贝,而是在__post_init__()
中初始化。 通常预期init=False
字段将很少能被正确地使用。 如果要使用它们,那么更明智的做法是使用另外的类构造器,或者自定义的replace()
(或类似名称) 方法来处理实例的拷贝。数据类支持也被泛型函数
copy.replace()
所支持。
- dataclasses.is_dataclass(obj)¶
如果其形参是一个 dataclass(包括 dataclass 的子类)或其实例则返回
True
,否则返回False
。如果你需要知道一个类是否是一个数据类的实例(而不是一个数据类本身),那么再添加一个
not isinstance(obj, type)
检查:def is_dataclass_instance(obj): return is_dataclass(obj) and not isinstance(obj, type)
- dataclasses.MISSING¶
一个指明“没有提供 default 或 default_factory”的监视值。
- dataclasses.KW_ONLY¶
一个用途类型标的监视值。 任何在伪字段之后的类型为
KW_ONLY
的字段会被标记为仅限关键字的字段。 请注意在其他情况下KW_ONLY
类型的伪字段会被完全忽略。 这包括此类字段的名称。 根据惯例,名称_
会被用作KW_ONLY
字段。 仅限关键字字段指明当类被实例化时__init__()
形参必须以关键字形式来指定。在这个例子中,字段
y
和z
将被标记为仅限关键字字段:@dataclass class Point: x: float _: KW_ONLY y: float z: float p = Point(0, y=1.5, z=2.0)
在单个数据类中,指定一个以上
KW_ONLY
类型的字段将导致错误。Added in version 3.10.
- exception dataclasses.FrozenInstanceError¶
在定义时设置了
frozen=True
的类上调用隐式定义的__setattr__()
或__delattr__()
时引发。 这是AttributeError
的一个子类。
初始化后处理¶
- dataclasses.__post_init__()¶
当在类上定义时,它将被所生成的
__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
The __init__()
method generated by @dataclass
does not call base
class __init__()
methods. If the base class has an __init__()
method
that has to be called, it is common to call this method in a
__post_init__()
method:
class Rectangle:
def __init__(self, height, width):
self.height = height
self.width = width
@dataclass
class Square(Rectangle):
side: float
def __post_init__(self):
super().__init__(self.side, self.side)
但是,请注意一般来说数据类生成的 __init__()
方法不需要被调用,因为派生的数据类将负责初始化任何本身为数据类的基类的所有字段。
请参阅下面有关仅初始化变量的小节来了解如何将形参传递给 __post_init__()
。 另请参阅关于 replace()
如何处理 init=False
字段的警告。
类变量¶
One of the few places where @dataclass
actually inspects the type
of a field is to determine if a field is a class variable as defined
in PEP 526. It does this by checking if the type of the field is
typing.ClassVar
. If a field is a ClassVar
, it is excluded
from consideration as a field and is ignored by the dataclass
mechanisms. Such ClassVar
pseudo-fields are not returned by the
module-level fields()
function.
仅初始化变量¶
Another place where @dataclass
inspects a type annotation is to
determine if a field is an init-only variable. It does this by seeing
if the type of a field is of type InitVar
. If a field
is an InitVar
, it is considered a pseudo-field called an init-only
field. As it is not a true field, it is not returned by the
module-level fields()
function. Init-only fields are added as
parameters to the generated __init__()
method, and are passed to
the optional __post_init__()
method. They are not otherwise used
by dataclasses.
例如,假设在创建类时没有为某个字段提供值,初始化时将从数据库中取值:
@dataclass
class C:
i: int
j: int | None = None
database: InitVar[DatabaseType | None] = 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)
冻结的实例¶
It is not possible to create truly immutable Python objects. However,
by passing frozen=True
to the @dataclass
decorator you can
emulate immutability. In that case, dataclasses will add
__setattr__()
and __delattr__()
methods to the class. These
methods will raise a FrozenInstanceError
when invoked.
在使用 frozen=True
时会有微小的性能损失: __init__()
不能使用简单赋值来初始化字段,而必须使用 object.__setattr__()
。
继承¶
When the dataclass is being created by the @dataclass
decorator,
it looks through all of the class's base classes in reverse MRO (that
is, starting at object
) and, for each dataclass that it finds,
adds the fields from that base class to an ordered mapping of fields.
After all of the base class fields are added, it adds its own fields
to the ordered mapping. All of the generated methods will use this
combined, calculated ordered mapping of fields. Because the fields
are in insertion order, derived classes override base classes. An
example:
@dataclass
class Base:
x: Any = 15.0
y: int = 0
@dataclass
class C(Base):
z: int = 10
x: int = 15
最终的字段列表依次是 x
, y
, z
。 最终的 x
类型是 int
,正如类 C
中所指定的。
为 C
生成的 __init__()
方法看起来像是这样:
def __init__(self, x: int = 15, y: int = 0, z: int = 10):
__init__()
中仅限关键字形参的重新排序¶
在计算出 __init__()
所需要的形参之后,任何仅限关键字形参会被移至所有常规(非仅限关键字)形参的后面。 这是 Python 中实现仅限关键字形参所要求的:它们必须位于非仅限关键字形参之后。
在这个例子中,Base.y
, Base.w
和 D.t
是仅限关键字字段,而 Base.x
和 D.z
是常规字段:
@dataclass
class Base:
x: Any = 15.0
_: KW_ONLY
y: int = 0
w: int = 1
@dataclass
class D(Base):
z: int = 10
t: int = field(kw_only=True, default=0)
为 D
生成的 __init__()
方法看起来像是这样:
def __init__(self, x: Any = 15.0, z: int = 10, *, y: int = 0, w: int = 1, t: int = 0):
请注意形参原来在字段列表中出现的位置已被重新排序:前面是来自常规字段的形参而后面是来自仅限关键字字段的形参。
仅限关键字形参的相对顺序会在重新排序的 __init__()
列表中保持不变。
默认工厂函数¶
如果一个 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
,正如预期的那样。
使用数据类,如果 此代码有效:
@dataclass
class D:
x: list = [] # 此代码将引发 ValueError
def add(self, element):
self.x.append(element)
它生成的代码类似于:
class D:
x = []
def __init__(self, x=x):
self.x = x
def add(self, element):
self.x.append(element)
assert D().x is D().x
This has the same issue as the original example using class C
.
That is, two instances of class D
that do not specify a value
for x
when creating a class instance will share the same copy
of x
. Because dataclasses just use normal Python class
creation they also share this behavior. There is no general way
for Data Classes to detect this condition. Instead, the
@dataclass
decorator will raise a ValueError
if it
detects an unhashable default parameter. The assumption is that if
a value is unhashable, it is mutable. This is a partial solution,
but it does protect against many common errors.
使用默认工厂函数是一种创建可变类型新实例的方法,并将其作为字段的默认值:
@dataclass
class D:
x: list = field(default_factory=list)
assert D().x is not D().x
描述器类型的字段¶
当字段被 描述器对象 赋值为默认值时会遵循以下行为:
传递给数据类的
__init__()
方法的字段值会被传递给描述器的__set__()
方法而不会覆盖描述器对象。类似地,当获取或设置字段值时,将调用描述器的
__get__()
或__set__()
方法而不是返回或重写描述器对象。To determine whether a field contains a default value,
@dataclass
will call the descriptor's__get__()
method using its class access form:descriptor.__get__(obj=None, type=cls)
. If the descriptor returns a value in this case, it will be used as the field's default. On the other hand, if the descriptor raisesAttributeError
in this situation, no default value will be provided for the field.
class IntConversionDescriptor:
def __init__(self, *, default):
self._default = default
def __set_name__(self, owner, name):
self._name = "_" + name
def __get__(self, obj, type):
if obj is None:
return self._default
return getattr(obj, self._name, self._default)
def __set__(self, obj, value):
setattr(obj, self._name, int(value))
@dataclass
class InventoryItem:
quantity_on_hand: IntConversionDescriptor = IntConversionDescriptor(default=100)
i = InventoryItem()
print(i.quantity_on_hand) # 100
i.quantity_on_hand = 2.5 # 调用 __set__ 并传入 2.5
print(i.quantity_on_hand) # 2
若一个字段的类型是描述器,但其默认值并不是描述器对象,那么该字段只会像普通的字段一样工作。