数据类-数据类

源代码: Lib/dataclasses.py


此模块提供装饰器和Function,用于将生成的special method(例如init()repr())自动添加到用户定义的类。最初是在 PEP 557中描述的。

在这些生成的方法中使用的成员变量是使用 PEP 526类型 Comments 定义的。例如此代码:

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 版中的新Function。

模块级装饰器,类和函数

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

dataclass()装饰器检查该类以找到fieldfield定义为具有type annotation的类变量。除了以下所述的两个 exception,dataclass()中的任何内容都不会检查变量 Comments 中指定的类型。

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

dataclass()装饰器将向该类添加各种“ dunder”方法,如下所述。如果类中已经存在任何添加的方法,则行为取决于参数,如下所述。装饰器返回与调用相同的类;没有创建新的类。

如果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:如果为 true(默认值),将生成init()方法。

如果该类已定义init(),则忽略此参数。

  • repr:如果为 true(默认值),将生成repr()方法。生成的 repr 字符串将具有类名以及每个字段的名称和 repr(按在类中定义的 Sequences)。不包括标记为从代表中排除的字段。例如:InventoryItem(name='widget', unit_price=3.0, quantity_on_hand=10)

如果该类已定义repr(),则忽略此参数。

  • eq:如果为 true(默认值),将生成eq()方法。此方法按 Sequences 比较该类,就好像它是其字段的 Tuples 一样。比较中的两个实例必须具有相同的类型。

如果该类已定义eq(),则忽略此参数。

  • order:如果为 true(默认值为False),则将生成lt()le()gt()ge()方法。它们按 Sequences 比较该类,就好像它是其字段的 Tuples 一样。比较中的两个实例必须具有相同的类型。如果order为 true 且eq为 false,则引发ValueError

如果该类已经定义了lt()le()gt()ge()中的任何一个,则将引发TypeError

  • unsafe_hash:如果为False(默认值),则根据eqfrozen的设置方式生成hash()方法。

hash()由内置hash()使用,并且在将对象添加到哈希集合(例如字典和集合)时使用。具有hash()表示该类的实例是不可变的。可变性是一个复杂的属性,取决于程序员的意图,eq()的存在和行为以及dataclass()装饰器中eqfrozen标志的值。

默认情况下,除非安全,否则dataclass()不会隐式添加hash()方法。它既不会添加也不会更改现有的显式定义的hash()方法。如hash()文档中所述,设置类属性__hash__ = None对 Python 具有特定的含义。

如果未明确定义hash()或将其设置为None,则dataclass() 可以添加隐式hash()方法。尽管不建议这样做,但是您可以强制dataclass()使用unsafe_hash=True创建hash()方法。如果您的类在逻辑上是不可变的,但仍然可以变更,则可能是这种情况。这是一个特殊的用例,应仔细考虑。

以下是 Managementhash()方法的隐式创建的规则。请注意,您不能在数据类中都具有显式的hash()方法并设置unsafe_hash=True;这将导致TypeError

如果eqfrozen都为 true,则默认情况下dataclass()将为您生成hash()方法。如果eq为 true 且frozen为 false,则hash()将设置为None,将其标记为不可散列(由于它是可变的,因此它是不可散列的)。如果eq为 false,则hash()将保持不变,这意味着将使用超类的hash()方法(如果超类为object,则意味着它将退回到基于 id 的哈希)。

  • frozen:如果为 true(默认值为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 *)
    • 对于常见和简单的用例,不需要其他Function。但是,有些数据类Function需要附加的每个字段信息。为了满足对其他信息的需求,您可以使用对提供的field()函数的调用来替换默认字段值。例如:
@dataclass
class C:
    mylist: List[int] = field(default_factory=list)

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

如上所示,MISSING值是一个哨兵对象,用于检测是否提供了defaultdefault_factory参数。使用此标记是因为Nonedefault的有效值。没有代码应直接使用MISSING值。

field()的参数为:

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

  • default_factory:如果提供,则必须为零参数可调用对象,当此字段需要默认值时将被调用。除其他目的外,这可用于指定具有可变默认值的字段,如下所述。同时指定defaultdefault_factory是错误的。

  • init:如果为 true(默认值),则此字段作为生成的init()方法的参数包括在内。

  • repr:如果为 true(默认值),则此字段包含在所生成的repr()方法返回的字符串中。

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

  • hash:可以是 bool 或None。如果为 true,则此字段包含在生成的hash()方法中。如果是None(默认值),请使用compare的值:通常这是预期的行为。如果将字段用于比较,则应在哈希中考虑该字段。不建议将此值设置为None以外的任何值。

设置hash=Falsecompare=True的一个可能原因是,如果某个字段的哈希值计算成本很高,则该字段需要进行相等性测试,并且还有其他字段会影响该类型的哈希值。即使将字段从哈希中排除,该字段仍将用于比较。

  • metadata:这可以是 Map 或无。没有一个被视为空字典。此值包装在MappingProxyType()中以使其为只读,并在Field对象上公开。数据类完全不使用它,它是作为第三方扩展机制提供的。多个第三方可以各自具有自己的密钥,以用作元数据中的命名空间。

如果pass调用field()指定了字段的默认值,则此字段的 class 属性将由指定的default值替换。如果未提供default,则将删除 class 属性。目的是在dataclass()装饰器运行之后,类属性都将包含字段的默认值,就像指定了默认值本身一样。例如,之后:

@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.xC.y

  • 类别 dataclasses. Field
    • Field个对象描述了每个定义的字段。这些对象是在内部创建的,并由fields()模块级方法返回(请参见下文)。用户切勿直接实例化Field对象。它记录的属性是:

Note

  • name:字段名称。

  • type:字段的类型。

  • defaultdefault_factoryinitreprhashcomparemetadata具有与field()语句中相同的含义和值。

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

  • dataclasses. fields(* class_or_instance *)

    • 返回定义此数据类的字段的Field对象的 Tuples。接受数据类或数据类的实例。如果未传递数据类或实例的实例,则引发TypeError。不返回ClassVarInitVar的伪字段。
  • dataclasses. asdict(* instance **,* dict_factory = dict *)

    • pass使用工厂函数dict_factory将数据类instance转换为字典。每个数据类都以name: value对的形式转换为其字段的字典。数据类,字典,列表和 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}]}

如果instance不是数据类实例,则引发TypeError

  • dataclasses. astuple(* instance **,* tuple_factory = tuple *)
    • 将数据类instance转换为 Tuples(pass使用工厂函数tuple_factory)。每个数据类都将转换为其字段值的 Tuples。数据类,字典,列表和 Tuples 都递归到其中。

continue 上一个示例:

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

如果instance不是数据类实例,则引发TypeError

  • 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中给出的 Base Class,并使用在namespace中给出的名称空间初始化。 fields是一个可迭代的元素,其元素分别是name(name, type)(name, type, Field)。如果仅提供name,则将typing.Any用于typeinitrepreqorderunsafe_hashfrozen的值与在dataclass()中具有相同的含义。

此函数不是严格必需的,因为使用__annotations__创建新类的任何 Python 机制都可以应用dataclass()函数将该类转换为数据类。提供此Function是为了方便。例如:

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

pass返回数据类的init()方法可以创建新返回的对象。这样可以确保也调用__post_init__()(如果存在)。

如果没有默认值,则仅初始化变量(如果有)必须在对replace()的调用中指定,以便可以将它们传递给init()__post_init__()

changes包含定义为具有init=False的任何字段是错误的。在这种情况下,将引发ValueError

警告有关在replace()的调用期间init=False字段如何工作的信息。它们不是从源对象复制而来的,而是在__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)

Post-init processing

如果在类上定义了__post_init__(),则生成的init()代码将调用名为__post_init__()的方法。通常将其称为self.__post_init__()。但是,如果定义了任何InitVar字段,它们也将按照在类中定义的 Sequences 传递给__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字段的警告。

Class variables

dataclass()实际检查字段类型的两个地方之一是确定字段是否是 PEP 526中定义的类变量。它pass检查字段的类型是否为typing.ClassVar来完成此操作。如果字段是ClassVar,则将其从字段中排除,并被数据类机制忽略。模块级fields()函数不会返回此类ClassVar伪字段。

Init-only variables

dataclass()检查类型 Comments 的另一个地方是确定字段是否为仅初始化变量。它pass查看字段的类型是否为dataclasses.InitVar来做到这一点。如果字段是InitVar,则将其视为称为仅初始化字段的伪字段。由于它不是真实字段,因此模块级fields()函数不会返回它。仅初始化字段作为参数添加到生成的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()将为ij返回Field个对象,但不会为database返回Field个对象。

Frozen instances

无法创建 true 不变的 Python 对象。但是,pass将frozen=True传递给dataclass()装饰器,您可以模拟不变性。在这种情况下,数据类将向该类添加setattr()delattr()方法。这些方法在调用时将引发FrozenInstanceError

使用frozen=True时,性能会受到很小的影响:init()不能使用简单的赋值来初始化字段,而必须使用object.setattr()

Inheritance

dataclass()装饰器创建数据类时,它将以反向 MRO(即,从object开始)遍历该类的所有 Base Class,并针对找到的每个数据类,将该 Base Class 中的字段添加到有序对象中字段 Map。添加所有 Base Class 字段后,它将自己的字段添加到有序 Map 中。所有生成的方法都将使用此组合的,经过计算的字段有序 Map。由于字段按插入 Sequences 排列,因此派生类将覆盖 Base Class。一个例子:

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

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

字段的finally列表按 Sequences 是xyzx的finally类型是int,如C类中所指定。

C生成的init()方法将如下所示:

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

默认出厂Function

Note

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

mylist: list = field(default_factory=list)

如果从init()(使用init=False)中排除了某个字段,并且该字段还指定了default_factory,则将始终从生成的init()函数中调用默认工厂函数。发生这种情况是因为没有其他方法可以为该字段提供初始值。

可变的默认值

Note

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 = []
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的原始示例具有相同的问题。也就是说,创建类实例时未为x指定值的两个类D实例将共享x的相同副本。因为数据类仅使用常规的 Python 类创建,所以它们也共享此行为。数据类没有检测这种情况的通用方法。相反,如果数据类检测到类型为listdictset的默认参数,则它将引发TypeError。这是部分解决方案,但确实可以防止许多常见错误。

使用默认工厂Function是一种创建可变类型的新实例作为字段默认值的方法:

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

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

Exceptions

  • exception dataclasses. FrozenInstanceError
    • 在用frozen=True定义的数据类上调用隐式定义的setattr()delattr()时引发。