"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 新版功能.


Module-level decorators, classes, and functions
===============================================

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

   这个函数是 *decorator* ，用于将生成的 *special method* 添加到类中，
   如下所述。

   "dataclass()" 装饰器会检查类以查找 "field" —— "field" 被定义为具有
   *类型标注* 的类变量。除了下面描述的两个例外，在 "dataclass()" 中没
   有什么东西会去检查这些变量标注成了何种类型。

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

   "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": 如果为真值（默认），将生成一个 "__init__()" 方法。

     如果类已定义 "__init__()" ，则忽略此参数。

   * "repr" ：如果为真值（默认），将生成一个 "__repr__()" 方法。 生成
     的 repr 字符串将具有类名以及每个字段的名称和 repr ，按照它们在类
     中定义的顺序。不包括标记为从 repr 中排除的字段。 例如：
     "InventoryItem(name='widget', unit_price=3.0,
     quantity_on_hand=10)"。

     如果类已定义 "__repr__()" ，则忽略此参数。

   * "eq" ：如果为true（默认值），将生成 "__eq__()" 方法。此方法将类作
     为其字段的元组按顺序比较。比较中的两个实例必须是相同的类型。

     如果类已定义 "__eq__()" ，则忽略此参数。

   * "order" ：如果为真值（默认为 "False" ），则 "__lt__()" 、
     "__le__()" 、 "__gt__()" 和 "__ge__()" 方法将生成。 这将类作为其
     字段的元组按顺序比较。比较中的两个实例必须是相同的类型。如果
     "order" 为真值并且 "eq" 为假值 ，则引发 "ValueError" 。

     如果类已经定义了 "__lt__()" 、 "__le__()" 、 "__gt__()" 或者
     "__ge__()" 中的任意一个，将引发 "TypeError" 。

   * "unsafe_hash" ：如果为 "False" （默认值），则根据 "eq" 和
     "frozen" 的设置方式生成 "__hash__()" 方法。

     "__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" 都是 true，默认情况下 "dataclass()" 将为你
     生成一个 "__hash__()" 方法。如果 "eq" 为 true 且 "frozen" 为
     false ，则 "__hash__()" 将被设置为 "None" ，标记它不可用（因为它
     是可变的）。如果 "eq" 为 false ，则 "__hash__()" 将保持不变，这意
     味着将使用超类的 "__hash__()" 方法（如果超类是 "object" ，这意味
     着它将回到基于id的hash）。

   * "frozen": 如为真值 (默认值为 "False")，则对字段赋值将会产生异常。
     这模拟了只读的冻结实例。 如果在类中定义了 "__setattr__()" 或
     "__delattr__()" 则将会引发 "TypeError"。 参见下文的讨论。

   可以用普通的 Python 语法为各个 "field" 指定默认值：

      @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]

   As shown above, the "MISSING" value is a sentinel object used to
   detect if the "default" and "default_factory" parameters are
   provided.  This sentinel is used because "None" is a valid value
   for "default".  No code should directly use the "MISSING" value.

   "field()" 的形参有：

   * "default"：如果提供，这将是该字段的默认值。设计这个形参是因为
     "field()" 调用将会占据原来用来提供默认值的位置。

   * "default_factory"：如果提供，它必须是一个需要零个参数的可调用对象
     ，当该字段需要一个默认值时，它将被调用。这能解决当默认值是可变对
     象时会带来的问题，如下所述。同时指定 "default" 和
     "default_factory" 将产生错误。

   * "init" ：如果为true（默认值），则该字段作为参数包含在生成的
     "__init__()" 方法中。

   * "repr" ：如果为true（默认值），则该字段包含在生成的 "__repr__()"
     方法返回的字符串中。

   * "compare" ：如果为true（默认值），则该字段包含在生成的相等性和比
     较方法中（ "__eq__()" ， "__gt__()" 等等）。

   * "hash" ：这可以是布尔值或 "None" 。如果为true，则此字段包含在生成
     的 "__hash__()" 方法中。如果为 "None" （默认值），请使用
     "compare" 的值，这通常是预期的行为。如果字段用于比较，则应在 hash
     中考虑该字段。不鼓励将此值设置为 "None" 以外的任何值。

     设置 "hash=False" 但 "compare=True" 的一个合理情况是，一个计算哈
     希值的代价很高的字段是检验等价性需要的，且还有其他字段可以用于计
     算类型的哈希值。可以从哈希值中排除该字段，但仍令它用于比较。

   * "metadata"：可以是映射或 None。None 被视为一个空的字典。这个值将
     被包装在 "MappingProxyType()" 中，使其只读，并暴露在 "Field" 对象
     上。数据类不使用它——它是作为第三方扩展机制提供的。多个第三方可以
     各自拥有自己的键，以用作元数据中的命名空间。

   如果通过调用 "field()" 指定字段的默认值，则该字段对应的类属性的值将
   最终被替换为指定的 "default" 值。如果没有提供 "default"，那么将删除
   该字段对应的类属性。目的是在 "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.x" 和
   "C.y" 将不设置。

class dataclasses.Field

   "Field" 对象描述每个已定义的字段。这些对象在内部被创建，并由
   "fields()" 模块级方法返回（见下）。用户永远不应该直接实例化 "Field"
   对象。它的下列属性的含义是由文档规定的：

      * "name"：字段的名称。

      * "type"：字段的类型。

      * "default", "default_factory", "init", "repr", "hash",
        "compare", and "metadata" have the identical meaning and
        values as they do in the "field()" declaration.

   可能存在其他属性，但它们是私有的。用户不应检查或依赖于这些属性。

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}]}

   要创建一个浅拷贝，可以使用以下的变通方法：

      dict((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)

   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 just "name" is supplied, "typing.Any" is used for
   "type".  The values of "init", "repr", "eq", "order",
   "unsafe_hash", and "frozen" have the same meaning as they do in
   "dataclass()".

   此函数不是必需的，因为任何用于创建带有 "__annotations__" 的新类的
   Python 机制都可以进一步用 "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(obj, /, **changes)

   创建一个与 "obj" 类型相同的新对象，将字段替换为 "changes" 里的值。
   如果 "obj" 不是数据类，则抛出 "TypeError" 。如果 "changes" 里的值没
   有指定要替换的字段名，则抛出 "TypeError"。

   新返回的对象通过调用数据类的 "__init__()" 方法创建。这确保了如果存
   在 "__post_init__()" ，其也被调用。

   如果存在没有默认值的仅初始化变量，必须在调用 "replace()" 时指定，以
   便它们可以传递给 "__init__()" 和 "__post_init__()" 。

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

   提前提醒，  字段在 "replace()" 被调用时的行为是，当它们被初始化时，
   不是从源对象直接复制，而是在 "__post_init__()" 中初始化。除非保持审
   慎，否则 "init=False" 字段大概很少能被正确地使用。如果有使用
   "init=False" 的字段，那么使用另外的类构造器，或自定义 "replace()"
   方法（或类似名称的方法）来复制实例，可能会更好。

dataclasses.is_dataclass(obj)

   如果其形参为数据类，或其实例，返回 "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

由 "dataclass()" 所生成的 "__init__()" 方法不会调用基类的 "__init__()"
方法。 如果基类有需要被调用的 "__init__()" 方法，通常是在
"__post_init__()" 方法中调用此方法:

   @dataclass
   class Rectangle:
       height: float
       width: float

   @dataclass
   class Square(Rectangle):
       side: float

       def __post_init__(self):
           super().__init__(self.side, self.side)

但是请注意，一般来说 dataclass 生成的 "__init__()" 方法不需要被调用，
因为派生的 dataclass 将负责初始化任何自身为 dataclass 的基类的所有字段
。

有关将参数传递给 "__post_init__()" 的方法，请参阅下面有关仅初始化变量
的段落。另请参阅关于 "replace()" 处理 "init=False" 字段的警告。


类变量
======

One of two 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.


仅初始化变量
============

The other 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 "dataclasses.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
       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()" 将返回 "i" 和 "j" 的 "Field" 对象，但不包括
"database" 。


冻结的实例
==========

无法创建真正不可变的 Python 对象。但是，通过将 "frozen=True" 传递给
"dataclass()" 装饰器，你可以模拟不变性。在这种情况下，数据类将向类添加
"__setattr__()" 和 "__delattr__()" 方法。 些方法在调用时会引发
"FrozenInstanceError" 。

使用 "frozen=True" 时会有很小的性能损失： "__ init__()" 不能使用简单的
赋值来初始化字段，并必须使用 "object.__setattr__()"。


继承
====

当数据类由 "dataclass()" 装饰器创建时，它会按反向 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" 中所指定的那样。

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

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


默认工厂函数
============

   如果一个 "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 = []
          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

   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, dataclasses will raise a
   "TypeError" if it detects a default parameter of type "list",
   "dict", or "set".  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


异常
====

exception dataclasses.FrozenInstanceError

   在使用 "frozen=True" 定义的数据类上调用隐式定义的 "__setattr__()"
   或 "__delattr__()" 时引发。 这是 "AttributeError" 的一个子类。
