> For the complete documentation index, see [llms.txt](https://docs.xiwind-corp.com/tech/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.xiwind-corp.com/tech/python-library/dataclasses-shu-ju-class.md).

# dataclasses 【數據 Class】

## 簡介

***

`dataclasses` module 用於向用戶定義的 Class 新增 special methods (特殊方法)，例如：`__init__` 或 `__repr__`。

使用 Dataclasses 的主要優點是在建立 Class 時減少 [Boilerplate code](https://en.wikipedia.org/wiki/Boilerplate_code)，特別是那些主要儲存數據，但沒有太多行為 (方法) 的 Class。

Dataclasses 自動向 Class 加入 special methods，包括：`__init__`、`__repr__` 和 `__eq__`。 這些 special methods 手動編寫通常很乏味，特別是對於具有許多屬性的 Class。如果需要，也可以自定義和微調這些方法的行為。

數據類的裝飾器是 `@dataclasses.dataclass`。 這是一個簡單的例子：

{% code title="PYTHON" %}

```python
import dataclasses

@dataclasses.dataclass
class Point:
    x: int
    y: int

# Create instances of Point (__init__)
p1 = Point(2, 3)
p2 = Point(2, 3)
p3 = Point(3, 4)

# Demonstrates __repr__
print(p1)        # Output: Point(x=2, y=3)

# Demonstrates __eq__
print(p1 == p2)  # Output: True
print(p1 == p3)  # Output: False
```

{% endcode %}

Class `Point` 有兩個 fields `x` 和 `y`，這 2 個 fields 都應該是 `int`。

`@dataclass` 自動加入一個以 `x` 和 `y` 作為參數的 `__init__` 方法，一個返回物件的字串表示形式的 `__repr__` 方法，以及一個 `__eq__` 方法，允許根據 `Point` 的兩個實例的 `x` 屬性和 `y` 屬性來比較它們的相等性。

### Field

***

在 Python Dataclasses 的 context 中，field 是綁定到 Class 的變數，並且該 class 的每個實例都將具有其自己的單獨的 field 副本，來存儲特定於實例的狀態。

在傳統的物件導向術語中，這些通常被稱為 "attributes" or "properties"。Dataclasses 中的 fields 直接在 Class 中定義，並且可以指定它們的類型和預設值。

{% code title="PYTHON" %}

```python
import dataclasses

@dataclasses.dataclass
class Person:
    name: str
    age: int = 0
```

{% endcode %}

在此範例中，`name` 和 `age` 是 `Person` class 的 fields。

`name` field 應為 `str`，`age` field 應為 `int`，預設值為 0。每個 `Person` 實例都有自己的 `name` 和 `age` fields。

## @dataclass

***

`@dataclass` 用於生成的 special methods，然後新增到 Class 中。

當 `@dataclass` 應用於 Class 時，它會掃描該 Class 的 fields。 在這種情況下，fields 是包含類型註釋的 Class-level 變數。

{% code title="PYTHON" %}

```python
@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)
```

{% endcode %}

<table><thead><tr><th width="179">參數</th><th>說明</th></tr></thead><tbody><tr><td><strong>init</strong></td><td>是否將生成 <code>__init__</code> 方法。<br>如果 Class 已經定義了 <code>__init__</code>，則忽略此參數。</td></tr><tr><td><strong>repr</strong></td><td>是否將生成 <code>__repr__</code> 方法。<br>生成的 repr String 將包含 Class 名稱以及每個 field 的名稱，按照它們在 Class 中定義的順序排列。 不包括標記為從 repr 中排除的 fields 。如果 Class 已經定義了 <code>__repr__</code>，則忽略此參數。</td></tr><tr><td><strong>eq</strong></td><td>是否將生成 <code>__eq__</code> 方法。<br>此方法按順序比較 Class，僅當它們對應的 fields 相等時，該 Class 的兩個實例才會被比較為相等。如果 Class 已經定義了 <code>__eq__</code> ，則忽略此參數。</td></tr><tr><td><strong>order</strong></td><td>是否將生成 <code>__lt__</code> (小於)、<code>__le__</code> (小於或等於)、<code>__gt__</code> (大於) 和 <code>__ge__</code> (大於或等於) 方法。<br>這些使得 Class 的實例可排序且具有可比性。如果 field's type 不支援比較，這將引發 TypeError。如果 Class 已經定義了 <code>__lt__</code>、<code>__le__</code>、<code>__gt__</code> 和 <code>__ge__</code> 中的任何一個，則會引發 TypeError。</td></tr><tr><td><strong>unsafe_hash</strong></td><td>如果為 <code>True</code>， 會強制生成 <code>__hash__</code> 方法，但不建議這樣做，因為它違反了 Python's object model，其中 mutable 物件是 unhashable。<br>如果為 <code>False</code>，則根據 <code>eq</code> 和 <code>frozen</code> 的設置方式生成 <code>__hash__</code> 方法。</td></tr><tr><td><strong>frozen</strong></td><td>在實例建立後，指派 fields 將會引發 Exception。<br>類似於其他語言中的唯讀 (凍結；frozen) 數據類型，就像 Tuple 一樣。如果 Class 中定義了 <code>__setattr__</code> 或 <code>__delattr__</code>，則會引發 TypeError。</td></tr><tr><td><strong>match_args</strong></td><td></td></tr><tr><td><strong>kw_only</strong></td><td></td></tr><tr><td><strong>slots</strong></td><td></td></tr><tr><td><strong>weakref_slot</strong></td><td></td></tr></tbody></table>

{% hint style="info" %}
**生成的方法按照 fields 在 Class 中定義的順序使用 fields。**&#x20;

例如: `__init__` 方法中的參數順序遵循 Class 中 field 定義的順序。
{% endhint %}

{% hint style="info" %}
**以下是管理 `__hash__` 方法隱式建立的規則。**&#x20;

如果 `frozen=True, eq=True`，則將生成 `__hash__` 方法。&#x20;

如果 `frozen=False, eq=True`，則 `__hash__` 方法將被設置為 None，將其標記為 unhashable。&#x20;

如果 `frozen=False, eq=False`，則 `__hash__` 將保持不變，這意味著將使用 Superclass 的 `__hash__` 方法 (如果 Superclass 是 `object`，將回退到  id-based hashing)。
{% endhint %}

{% hint style="info" %}
**默認情況下，dataclass() 不會隱式生成 `__hash__` 方法 (無法對實例使用 `hash()`)。 它也不會更改現有的顯式定義的 `__hash__` 方法。**
{% endhint %}

{% hint style="warning" %}
**您不能在 Class 中同時擁有顯式 `__hash__` 方法並設置 `unsafe_hash=True`；這將導致 TypeError。**
{% endhint %}

### 範例 – unsafe\_hash (參數)

***

{% code title="PYTHON" %}

```python
import dataclasses

@dataclasses.dataclass(unsafe_hash=True)
class Person:
    name: str
    age: int
    address: str

p = Person("John", 25, "123 Oak St")
print(p)       # Output: Person(name='John', age=25, address='123 Oak St')
print(hash(p)) # -537977084494215504

# =========================================================
@dataclasses.dataclass(unsafe_hash=True)
class AdvancedExample:
    name: str
    age: int
    mutable_field: dict  # mutable field which normally prevents __hash__()

adv_ex = AdvancedExample("John", 25, {"address": "123 Oak St", "phone": "123-456-7890"})

print(f"Hash of the object: {hash(adv_ex)}")
# Output: TypeError: unhashable type: 'dict'
```

{% endcode %}

## field()

***

該函數用於自定義 Data class 中的各個 fields。就像指定了預設值本身一樣。

{% code title="PYTHON" %}

```python
field(*, 
      default=MISSING, 
      default_factory=MISSING, 
      init=True, 
      repr=True, 
      hash=None, 
      compare=True, 
      metadata=None, 
      kw_only=MISSING)
```

{% endcode %}

<table><thead><tr><th width="184">參數</th><th>說明</th></tr></thead><tbody><tr><td><strong>default</strong></td><td>Field 的預設值。</td></tr><tr><td><strong>default_factory</strong></td><td>為 field 提供預設值的零參數函數，當該 field 需要預設值時將調用。 如果同時指定了 <code>default</code> 和 <code>default_factory</code>，則會引發 <code>TypeError</code>。</td></tr><tr><td><strong>init</strong></td><td>Field 將作為參數包含在生成的 <code>__init__</code> 方法中。</td></tr><tr><td><strong>repr</strong></td><td>Field 將包含在生成的 <code>__repr__</code> 方法，返回的 String 中。</td></tr><tr><td><strong>hash</strong></td><td>如果為 <code>True</code>，則該 field 包含在生成的 <code>__hash__</code> 方法中。<br>如果為 None (預設值)，則使用 <code>compare</code></td></tr><tr><td><strong>compare</strong></td><td>Field 將包含在生成的 <code>__eq__</code>, <code>__gt__</code>... 方法中。</td></tr><tr><td><strong>metadata</strong></td><td>將值包裝在 <a href="https://docs.python.org/3.12/library/types.html#types.MappingProxyType">MappingProxyType()</a> 中，以使其唯讀，並在 field 物件上公開。 它主要用於第三方擴展，Data Classes 本身不使用。</td></tr><tr><td><strong>kw_only</strong></td><td></td></tr></tbody></table>

{% hint style="info" %}
**`MISSING` 是一個** [**Sentinel value**](https://en.wikipedia.org/wiki/Sentinel_value) **物件，用於檢測使用者是否提供了某些參數。**&#x20;

使用此標記是因為 None 對於某些具有不同含義的參數來說是有效值。 任何程式碼都不應直接使用 `MISSING`。
{% endhint %}

### 範例 – 基礎

***

Data class `Employee` 有兩個 fields `name` 和 `age`，這兩個 fields 都具有使用 `dataclasses.field()` 指定的預設值。

{% code title="PYTHON" %}

```python
from dataclasses import dataclass, field

@dataclass
class Employee:
    name: str = field(default='John Doe')
    age: int = field(default=25)

employee = Employee()
print(employee)  # Output: Employee(name='John Doe', age=25)
```

{% endcode %}

**default\_factory 參數:** 當您想要生成每次都應該是新物件的預設值時，`default_factory` 非常有用。(例如：空 List 或當前時間戳)

{% code title="PYTHON" %}

```python
from datetime import datetime
from dataclasses import dataclass, field

@dataclass
class LogMessage:
    timestamp: datetime = field(default_factory=datetime.now)
    message: str = field(default="")

log1 = LogMessage()
print(log1)  
# Output: LogMessage(timestamp=datetime.datetime(2023, 7, 21, 2, 18, 12, 994307), message='')

log2 = LogMessage(message="Hello, World!")
print(log2)  
# Output: LogMessage(timestamp=datetime.datetime(2023, 7, 21, 2, 18, 12, 994307), message='Hello, World!')
```

{% endcode %}

**repr 參數:** `color` field 從 `__init__`, `__repr__` 方法中排除。因此在建立 `Point` 實例時不需要提供它，在打印 `Point` 物件時不包括它。

{% code title="PYTHON" %}

```python
from dataclasses import dataclass, field

@dataclass
class Point:
    x: float
    y: float
    color: str = field(init=False, repr=False, default='White')

p = Point(3.6, 4.8)
print(p)  # Output: Point(x=3.6, y=4.8)
```

{% endcode %}

### 範例 – Mutable 物件

***

如果 field 是 mutable (例如: List, Dictionary)，並且您為其提供預設值，則應該使用 `default_factory` 來提供一個函數，該函數在每次創建新實例時生成新值。 這避免了在多個實例之間共享相同的可變的值。

{% code title="PYTHON" %}

```python
import dataclasses

# Correct usage: default_factory for mutable default value
@dataclasses.dataclass
class Right:
    items: list = dataclasses.field(default_factory=list)

r1 = Right()
r2 = Right()
r1.items.append(1)
print(r1.items)  # Outputs: [1]
print(r2.items)  # Outputs: []

# =========================================================
# No default value
@dataclasses.dataclass
class Right:
    items: list

r1 = Right([])
r2 = Right([])
r1.items.append(1)
print(r1.items)  # Outputs: [1]
print(r2.items)  # Outputs: []

# =========================================================
# Incorrect usage: mutable default value
@dataclasses.dataclass
class Wrong:
    items: list = []
# ValueError: mutable default <class 'list'> for field items is not allowed: use default_factory
```

{% endcode %}

### fields()

***

`fields(class_or_instance)` 獲取為特定 Dataclasses 或 Dataclass 實例定義的每個 `Field` 物件的 metadata。

透過使用 `fields()`，我們可以對程式碼中的 Dataclasses 進行更詳細的控制和理解。 這對於大量使用 Dataclasses 的複雜程序或 libraries 特別有用。

{% hint style="info" %}
**雖然 `fields()` 函數提供有關 Dataclass 的有用訊息，但您應該謹慎使用它們，因為訪問 metadata 可能會減慢您的程序速度。 大多數時候，您只需要直接訪問 fields 的值。**
{% endhint %}

{% code title="PYTHON" %}

```python
from dataclasses import dataclass, fields

@dataclass
class Point:
    x: int
    y: int

print(fields(Point))
```

{% endcode %}

**執行結果:**

```txt
(Field(name='x',
       type=<class 'int'>,
       default=<dataclasses._MISSING_TYPE object at 0x000002D0DF83BE50>,
       default_factory=<dataclasses._MISSING_TYPE object at 0x000002D0DF83BE50>,
       init=True,
       repr=True,
       hash=None,
       compare=True,
       metadata=mappingproxy({}),
       kw_only=False,
       _field_type=_FIELD),
 Field(name='y',
       type=<class 'int'>,
       default=<dataclasses._MISSING_TYPE object at 0x000002D0DF83BE50>,
       default_factory=<dataclasses._MISSING_TYPE object at 0x000002D0DF83BE50>,
       init=True,
       repr=True,
       hash=None,
       compare=True,
       metadata=mappingproxy({}),
       kw_only=False,
       _field_type=_FIELD))
```

#### 範例 – 基礎

***

{% code title="PYTHON" %}

```python
import dataclasses

@dataclasses.dataclass
class ComplexClass:
    name: str
    value: int = dataclasses.field(default=42, repr=False)
    list_values: list = dataclasses.field(default_factory=list)

# Create an instance
complex_instance = ComplexClass('example')

# Retrieve and print fields information
for f in dataclasses.fields(ComplexClass):
    print(f'Name: {f.name}, Type: {f.type}, Default: {f.default if f.default is not dataclasses.MISSING else "No default"}, Repr: {f.repr}')

# You can also pass an instance of the class
for f in dataclasses.fields(complex_instance):
    print(f'Name: {f.name}, Type: {f.type}, Default: {f.default if f.default is not dataclasses.MISSING else "No default"}, Repr: {f.repr}')
```

{% endcode %}

**執行結果:**

```
Name: name, Type: <class 'str'>, Default: No default, Repr: True
Name: value, Type: <class 'int'>, Default: 42, Repr: False
Name: list_values, Type: <class 'list'>, Default: No default, Repr: True

Name: name, Type: <class 'str'>, Default: No default, Repr: True
Name: value, Type: <class 'int'>, Default: 42, Repr: False
Name: list_values, Type: <class 'list'>, Default: No default, Repr: True
```

### asdict()

***

`asdict(obj, *, dict_factory=dict)` 每個 Dataclass fields 都轉換為 Dictionary，作為 `name: value` pairs。

透過 `dict_factory`，將 Dataclass `obj` 轉換為 `dict`。(deep copy)

{% code title="PYTHON" %}

```python
from dataclasses import dataclass, asdict

@dataclass
class Point:
    x: int
    y: int

p = Point(10, 20)
print(asdict(p))  # Outputs: {'x': 10, 'y': 20}
```

{% endcode %}

#### 範例 – 具有複雜 field 類型的 Dataclass

***

{% code title="PYTHON" %}

```python
from dataclasses import dataclass, asdict

@dataclass
class Address:
    city: str
    country: str

@dataclass
class Person:
    name: str
    address: Address

person = Person('John', Address('NYC', 'USA'))
print(asdict(person))
```

{% endcode %}

**執行結果:**

```txt
Output: {'name': 'John', 
         'address': {'city': 'NYC', 'country': 'USA'}}
```

`collections.OrderedDict` 保證 fields 的顯示順序與 Dataclass 中定義的順序相同。

{% code title="" %}

```python
from pprint import pprint
from collections import OrderedDict
from dataclasses import dataclass, asdict, field

@dataclass
class Employee:
    name: str
    age: int
    skills: list = field(default_factory=list)

@dataclass
class Company:
    name: str
    employees: list = field(default_factory=list)

company = Company('Example Corp.', 
                  [Employee('John', 30, ['Python', 'Java']), 
                   Employee('Jane', 28, ['Go', 'Rust'])])

pprint(asdict(company))
pprint(asdict(company, dict_factory=OrderedDict))  
```

{% endcode %}

**執行結果:**

```txt
{'employees': [{'age': 30, 'name': 'John', 'skills': ['Python', 'Java']},
               {'age': 28, 'name': 'Jane', 'skills': ['Go', 'Rust']}],
 'name': 'Example Corp.'}

OrderedDict([('name', 'Example Corp.'),
             ('employees',
              [OrderedDict([('name', 'John'),
                            ('age', 30),
                            ('skills', ['Python', 'Java'])]),
               OrderedDict([('name', 'Jane'),
                            ('age', 28),
                            ('skills', ['Go', 'Rust'])])])])
```

#### 範例 – shallow\_copy(obj)

***

`asdict()` 方法創建一個 deep copy，它比 shallow copy 的計算量更大，特別是對於大型 Dataclasses。

如果您知道不需要修改 Dataclass 中的任何物件，並且只需要它的簡單 dictionary 表示形式，則 `shallow_copy(obj)` 可能會更有效。

或者，如果您想要維護 dataclass 實例，您可能需要使用 `shallow_copy(obj)`。

{% code title="PYTHON" %}

```python
from dataclasses import dataclass, asdict, fields

# =========================================================
# Deep copy

@dataclass
class Employee:
    name: str
    skills: list

emp = Employee('John', ['Python', 'Java'])
emp_dict = asdict(emp)

# Add a skill to the List
emp.skills.append('Go')
print(emp.skills)          # Output: ['Python', 'Java', 'Go']
print(emp_dict['skills'])  # Output: ['Python', 'Java']

# =========================================================
# Shallow copy

@dataclass
class Example:
    name: str
    skills: list

def shallow_copy(obj):
    return dict((field.name, getattr(obj, field.name)) for field in fields(obj))

ex = Example('John', ['Python', 'Java'])
copied_ex = shallow_copy(ex)

# Add a skill to the List
ex.skills.append('Go')
print(ex.skills)            # Output: ['Python', 'Java', 'Go']
print(copied_ex['skills'])  # Output: ['Python', 'Java', 'Go']
```

{% endcode %}

### astuple()

***

`astuple(obj, *, tuple_factory=tuple)` 每個 Dataclass fields 都轉換為 Tuple。透過 `dict_factory`，將 Dataclass `obj` 轉換為 `tuple`。

{% code title="PYTHON" %}

```python
from dataclasses import dataclass, astuple, fields

@dataclass
class Point:
    x: int
    y: int

@dataclass
class Line:
    start: Point
    end: Point

# Shallow copy
def shallow_copy(obj):  
    return tuple(getattr(obj, field.name) for field in fields(obj))

line = Line(Point(0, 0), Point(10, 20))
print(astuple(line))  # Output: ((0, 0), (10, 20))
```

{% endcode %}
