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是假的,AValueError提高了。如果类已经定义了
__lt__(),__le__(),__gt__()或__ge__()然后TypeError提高了。unsafe_hash如果False(默认),a__hash__()方法是根据eq和frozen被设置。__hash__()是内置的hash()以及将对象添加到hash集合(如字典和集合)时。有一个__hash__()表示类的实例是不可变的。易变性是一个复杂的属性,它取决于程序员的意图、存在和行为__eq__()以及eq和frozen中的flagdataclass()decorator。默认情况下,
dataclass()不会隐式添加__hash__()方法,除非这样做是安全的。它也不会添加或更改显式定义的现有__hash__()方法。设置类属性__hash__ = None对python有特定的意义,如中所述__hash__()文档。如果
__hash__()未显式定义,或者如果将其设置为None然后dataclass()may 添加隐式__hash__()方法。虽然不推荐,但您可以强制dataclass()创建一个__hash__()方法与unsafe_hash=True. 如果您的类在逻辑上是不可变的,但仍然可以发生变化,那么就可能发生这种情况。这是一个专门的用例,应该仔细考虑。以下是管理隐式创建
__hash__()方法。请注意,不能同时使用__hash__()数据类和集合中的方法unsafe_hash=True;这将导致TypeError.如果
eq和frozen默认情况下都是真的dataclass()将生成一个__hash__()方法。如果eq是真的frozen__hash__()将被设置为None,将其标记为不可清洗(因为它是可变的)。如果eq__hash__()将保持原样,意味着__hash__()将使用超类的方法(如果超类是object,这意味着它将返回到基于ID的散列)。frozen:如果为真(默认为False),分配给字段将生成异常。这将模拟只读冻结实例。如果__setattr__()或__delattr__()在类中定义,然后TypeError被提升。请参阅下面的讨论。
fields可以选择使用标准的python语法指定默认值:@dataclass class C: a: int # 'a' has no default value b: int = 0 # assign a default value for 'b'
在这个例子中,两者都是
a和b将包括在添加的__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值是用于检测default和default_factory提供参数。使用这个哨兵是因为None是的有效值default.任何代码都不应直接使用MISSING价值。参数
field()是:default:如果提供,这将是此字段的默认值。这是需要的,因为field()调用本身替换默认值的正常位置。default_factory:如果提供,则必须是可调用的零参数,当此字段需要默认值时将调用该参数。除其他用途外,它还可以用于指定具有可变默认值的字段,如下所述。指定两者都是错误的default和default_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.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具有相同的含义和价值field()宣言。
其他属性可能存在,但它们是私有的,不能检查或依赖。
- dataclasses.fields(class_or_instance)¶
返回的元组
Field定义此数据类的字段的对象。接受数据类或数据类的实例。引发TypeError如果没有传递一个数据类或实例。不返回以下伪字段:ClassVar或InitVar.
- 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. 价值观init,repr,eq,order,unsafe_hash和frozen与它们在中的含义相同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)
冻结的实例¶
无法创建真正不可变的Python对象。但是,路过 frozen=True 到 dataclass() 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
最后的字段列表是, x , y , z . 最后一种类型 x 是 int ,如类中所述 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如果它检测到类型的默认参数list,dict或set. 这是一个局部解决方案,但它确实可以防止许多常见错误。使用默认factory函数可以创建可变类型的新实例作为字段的默认值:
@dataclass class D: x: list = field(default_factory=list) assert D().x is not D().x
例外情况¶
- exception dataclasses.FrozenInstanceError¶
当隐式定义
__setattr__()或__delattr__()在用定义的数据类上调用frozen=True.