> 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/make_dataclass-replace-is_dataclass-__post_init__.md).

# make\_dataclass(), replace(), is\_dataclass(), \_\_post\_init\_\_

### make\_dataclass()

***

創建一個新的 Dataclass。

此函數並不是嚴格必需的，提供此函數只是為了方便。你可以使用 `dataclass()` 來代替 `make_dataclass()`。

{% code title="PYTHON" %}

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

{% endcode %}

<table><thead><tr><th width="188">參數</th><th>說明</th></tr></thead><tbody><tr><td><strong>cls_name</strong></td><td>Dataclass 名稱。</td></tr><tr><td><strong>fields</strong></td><td>fields 的定義，是一個 iterable 物件。<br><code>fields</code> 的元素可以是 <code>name</code>、<code>(name, type)</code> 或 <code>(name, type, Field)</code>。如果僅提供 <code>name</code> ，則轉變為 <code>(name, typing.Any)</code> (應用 <code>typing.Any</code>)。</td></tr><tr><td><strong>bases</strong></td><td>為正在建立的 Dataclass 指定多個 Base class。 生成的 Class 將從一個或多個 Base class 繼承。</td></tr><tr><td><strong>namespace</strong></td><td>向 Class 新增其他方法或屬性。<br>使用 <code>namespace</code> 中給定的命名空間進行初始化)。您可以提供一個 Dictionary，其中 Keys 是方法或屬性的名稱，Values 是實際的實現。</td></tr><tr><td><strong>module</strong></td><td>如果定義了<code>module</code>，則 Dataclass 的 <code>__module__</code> 屬性將設置為該值。 預設情況下，設置為調用者的 module 名稱。</td></tr></tbody></table>

{% hint style="info" %}
**`init`、`repr`、`eq`、`order`、`unsafe_hash`、`frozen`、`match_args`、`kw_only`、`slots` 和 `weakref_slot` 與它們在 `dataclass()` 中的含義相同。**
{% endhint %}

{% hint style="success" %}
**當您需要動態創建新 Classes 時，`make_dataclass()` 會非常有用。 對於靜態定義的 Classes，使用 `@dataclass` 可以更具可讀性。**
{% endhint %}

{% hint style="warning" %}
**`bases` 和 `namespace` 參數可以成為動態創建複雜 Dataclasses 的強大工具。 但是，它們也會使您的程式碼更難以理解和 debug。**&#x20;

如果您熟悉傳統的 Python Class 和繼承，在許多情況下，您最好使用 `@dataclass` 和傳統的 Class 來定義。
{% endhint %}

{% code title="PYTHON" %}

```python
from dataclasses import dataclass, make_dataclass

# =========================================================
# Creating a simple dataclass
Person = make_dataclass('Person', ['name', 'age', 'city'])

p = Person('John', 30, 'New York')
print(p)  # Output: Person(name='John', age=30, city='New York')

# =========================================================
# Use @dataclass
@dataclass
class Person:
    name: str
    age: int
    city: str

p = Person('John', 30, 'New York')
print(p)  # Output: Person(name='John', age=30, city='New York')
```

{% endcode %}

#### 範例 – 基礎

***

{% code title="PYTHON" %}

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

# =========================================================
fields = [
    ('name', str, field(default='Unknown')),
    ('age', int, field(default=0)),
    ('city', str, field(default='Unknown')),]

namespace = {
    'greet': lambda self: f"Hello, I'm {self.name} from {self.city}!"}

Person = make_dataclass('Person', fields, namespace=namespace)

p = Person('John', 30, 'New York')
print(p.greet())  # Output: Hello, I'm John from New York!

p = Person()
print(p.greet())  # Output: Hello, I'm Unknown from Unknown!

# =========================================================
# Use @dataclass
@dataclass
class Person:
    name: str = field(default='Unknown')
    age: int = field(default=0)
    city: str = field(default='Unknown')

    def greet(self):
        return f"Hello, I'm {self.name} from {self.city}!"

p = Person('John', 30, 'New York')
print(p.greet())  # Output: Hello, I'm John from New York!

p = Person()
print(p.greet())  # Output: Hello, I'm Unknown from Unknown!
```

{% endcode %}

#### 範例 – bases, namespace (參數)

***

{% code title="PYTHON" %}

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

# =========================================================
# Define a base class
class Mammal:
    def breathe(self):
        return "I can breathe!"

# Define the fields for the new dataclass
fields = [('name', str, field(default='Unknown'))]

# Define additional methods
namespace = {
    'greet': lambda self: f"Hello, I'm {self.name}!",}

# Create a new dataclass that inherits from Mammal
Person = make_dataclass('Person', fields, bases=(Mammal,), namespace=namespace)

p = Person('John')
print(p.greet())    # Output: Hello, I'm John!
print(p.breathe())  # Output: I can breathe!

# =========================================================
# Use @dataclass
class Mammal:
    def breathe(self):
        return "I can breathe!"

@dataclass
class Person(Mammal):
    name: str = field(default='Unknown')

    def greet(self):
        return f"Hello, I'm {self.name}!"

p = Person('John')
print(p.greet())    # Output: Hello, I'm John!
print(p.breathe())  # Output: I can breathe!

```

{% endcode %}

### replace()

***

建立一個新物件實例，與 `obj` 類型相同，且用 `changes` 中的值替換 fields。

{% code title="PYTHON" %}

```python
replace(obj, /, **changes)
```

{% endcode %}

當您想要建立新的實例改變某些 fields，但希望保持原始 Dataclass 實例不變時，`replace()` 函數非常有用。

例如：如果您有一個 Dataclass 實例，表示特定時間點某事物的狀態 ，並且您想要表示一個新狀態。但您不想更改原始實例，而是建立一個表示新狀態的新實例。

{% hint style="info" %}
**新返回的物件是透過調用 dataclass 的 `__init__()` 方法創建的。 這確保了`__post_init__` (如果存在)也會被調用。**
{% endhint %}

{% code title="PYTHON" %}

```python
from dataclasses import dataclass, replace

@dataclass
class Container:
    values: list

c = Container(values=[1, 2, 3, 4])

# Replace the 'values' list in the dataclass
c1 = replace(c, values=[10, 20, 30, 40])

print(c)   # Output: Container(values=[1, 2, 3, 4])
print(c1)  # Output: Container(values=[10, 20, 30, 40])
```

{% endcode %}

{% code title="PYTHON" %}

```python
from dataclasses import dataclass, replace

@dataclass
class Point:
    x: int
    y: int

@dataclass
class Line:
    start: Point
    end: Point

line1 = Line(start=Point(0, 0), 
            end=Point(10, 10))

# Replace the 'start' point of the line
line2 = replace(line1, start=Point(5, 5))

print(line1)  # Output: Line(start=Point(x=0, y=0), end=Point(x=10, y=10))
print(line2)  # Output: Line(start=Point(x=5, y=5), end=Point(x=10, y=10))
```

{% endcode %}

#### 範例 – init=False

***

在 Python 的 `dataclasses` 中，`init=False` field 是一個不打算作為參數傳遞給產生的 `__init__` 方法的 field。相反，預計該欄位將在 `__post_init__` 方法中，設定或保留為指定的預設值。

但是，當使用 `dataclasses.replace()` (目的在建立一個新物件作為現有物件的副本，並將某些欄位替換為新值) 時，`init=False` field 的行為有所不同。

它們不會從 "source" 物件複製。相反，它們將被設定為就像正在創建一個新物件一樣，這意味著它們將是：

1. 如果提供了預設值，則保留預設值。
2. 如果設定了它們，則在 `__post_init__` 中進行設定。
3. 未初始化。(如果以上都不成立)

{% code title="PYTHON" %}

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

@dataclass
class Product:
    name: str
    category: str = "General"  # This field has a default value and uses the init constructor
    id: int = field(default=None, init=False)  # This field is not included in the init constructor

    def __post_init__(self):
        if self.id is None:  # Initialize the id if not already set
            self.id = self.generate_id()

    def generate_id(self):
        # In a real scenario, this would generate a unique ID.
        return hash((self.name, self.category))


product = Product("Table")
print(product)      # Output: Product(name='Table', category='General', id=some_hash)

new_product = replace(product, name="Chair")
print(new_product)  # Output: Product(name='Chair', category='General', id=different_hash)
```

{% endcode %}

在這個例子中：

* `id` 是一個 `init=False` field。實例化後，呼叫 `__post_init__`，它設定 `id`。
* 當呼叫 `replace(product, name="Chair")` 時，會有效地建立一個新的 `Product` 實例，並將 `name` 設定為 `Chair`。 `id` 不是從 `product` 複製的，而是再次呼叫 `__post_init__`，根據新的 `name` 產生新的 `id`。

如果您期望 `replace()` 從原始物件複製所有 fields，這種行為可能會令人驚訝。因此，在使用 `init=False` fields 時，您應該意識到這一點。

如果您想要不同的行為，例如明確複製 `init=False` 字段，您可以提供自訂替換方法。自訂 `replace` 方法可確保 `init=False` field (本例中為 `id`) 被轉移到新實例，除非它被明確變更。

{% code title="PYTHON" %}

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

@dataclass
class Product:
    name: str
    category: str = "General"  # This field has a default value and uses the init constructor
    id: int = field(default=None, init=False)  # This field is not included in the init constructor

    def __post_init__(self):
        if self.id is None:  # Initialize the id if not already set
            self.id = self.generate_id()

    def generate_id(self):
        # In a real scenario, this would generate a unique ID.
        return hash((self.name, self.category))

    def custom_replace(self, **changes):
        # Manually create a copy of the object
        new_obj = Product(self.name, self.category)
        new_obj.id = self.id  # Explicitly copy the `init=False` field

        # Apply any requested changes
        for attr, value in changes.items():
            setattr(new_obj, attr, value)
        
        # Manually call __post_init__ if necessary
        new_obj.__post_init__()
        return new_obj


product = Product("Table")
print(product)      # Output: Product(name='Table', category='General', id=some_hash)

new_product = product.custom_replace(name="Chair")
print(new_product)  # Output: Product(name='Chair', category='General', id=same_hash_as_product)
```

{% endcode %}

### is\_dataclass()

***

`is_dataclass(obj)` 檢查給定的 `obj` 物件是否是 Dataclass 實例或 Dataclass 本身。 如果 `obj` 物件是 Dataclass 或 Dataclass 實例，則返回 `True`。

如果您需要知道一個 Class 是否是 Dataclass 的實例，而不是 Dataclass 本身。請進一步檢查 `isinstance(obj, type)`，即 `is_dataclass_instance(obj)`。

{% code title="PYTHON" %}

```python
def is_dataclass_instance(obj):
    return is_dataclass(obj) and not isinstance(obj, type)
```

{% endcode %}

**範例：**

{% code title="PYTHON" %}

```python
from dataclasses import dataclass, is_dataclass

@dataclass
class Person:
    name: str
    age: int

def is_dataclass_instance(obj):
    return is_dataclass(obj) and not isinstance(obj, type)

p = Person('John Doe', 30)

# Checking if the class is a dataclass or an instance of a dataclass
print(is_dataclass(Person))  # Output: True
print(is_dataclass(p))       # Output: True

print(is_dataclass_instance(Person))  # Output: False
print(is_dataclass_instance(p))       # Output: True
```

{% endcode %}

## \_\_post\_init\_\_

***

此方法在調用 data class 的 `__init__` 方法後立即調用，為初始化後處理提供方便的 hook。

這對於執行其他屬性初始化、驗證或建立 dataclass 的新實例時，需要進行的任何其他自訂設定非常有用。

**`__post_init__` 的用法：**

1. **初始化驗證：** 在物件初始化後使用 `__post_init__` 來驗證資料。在範例中，它檢查 `unit_price` 是否為負數，如果是則引發 `ValueError`。
2. **派生屬性：** 計算從傳遞給 `__init__` 方法的屬性派生的屬性。在範例中，`total_price` 是根據 `unit_price` 和 `quantity_on_hand` 計算得出的。
3. **類型檢查：** 執行自訂類型檢查。該範例確保 `serial_numbers` 是一個列表，如果不是，則會引發 `TypeError`。
4. **預設可變性：** 當使用可變預設值（例如: Lists, Dictionaries）時，使用 `default_factory`。這是因為在使用可變預設值時，預設值在 class 的所有實例之間共享，從而導致潛在的錯誤。
5. **副作用：** 如果您需要在建立實例後執行一些副作用， `__post_init__` 是一個好地方。(例如: logging (日誌記錄)、在某些 registry (註冊表) 中註冊等)
6. **Field 初始化：** 有時你需要設定不是 fields 的實例變數 (未在 class 中定義)。 `__post_init__` 可用來設定這些特定於實例的屬性，這些屬性不需要是 fields。
7. **附加屬性：** 也可以為 dataclass 實例新增未定義為 fields 的附加屬性，在範例中，為 `total_price`。
8. **Non-Field 預設值：** 對於初始化不應作為參數傳遞給 `__init__` 方法的屬性，您可以在`__post_init__` 中指派它們，可以使用直接值或基於其他 fields 的計算。

透過使用 `__post_init__`，您可以確保 data class 實例，在應用程式的其餘部分使用之前，始終處於有效狀態，從而使其成為強大且自我驗證的資料模型的強大功能。

{% code title="PYTHON" %}

```python
from dataclasses import dataclass, field
from typing import List, Any

@dataclass
class InventoryItem:
    """Class for keeping track of an item in inventory."""
    name: str
    unit_price: float
    quantity_on_hand: int = 0
    serial_numbers: List[str] = field(default_factory=list)
    
    def __post_init__(self):
        if self.unit_price < 0:
            raise ValueError("Unit price cannot be negative")
        # Automatically calculate total price and validate
        self.total_price: float = self.unit_price * self.quantity_on_hand
        # Perform type checking, raise error if serial_numbers is not a list
        if not isinstance(self.serial_numbers, list):
            raise TypeError("serial_numbers must be a list of strings")

    def add_serial_number(self, serial_number: str):
        self.serial_numbers.append(serial_number)

# Example usage
try:
    item = InventoryItem("Widget", 10.0, 100)
    item.add_serial_number("SN001")
    print(item)
except ValueError as e:
    print(e)
except TypeError as e:
    print(e)

# Output: InventoryItem(name='Widget', unit_price=10.0, quantity_on_hand=100, serial_numbers=['SN001'])
```

{% endcode %}

## MISSING, KW\_ONLY

***

* `MISSING` [Sentinel value](https://en.wikipedia.org/wiki/Sentinel_value) 僅提供 `default` 和 `default_factory` 設置預設值。

我們應該避免在應用程序代碼中使用 `MISSING` 。`MISSING` 僅在 `dataclasses` module 幕後使用，並發揮它魔法。

{% hint style="info" %}
**fields 的預設值是 `default=MISSING`，而不是 `default=None`。**&#x20;

這樣做主要是為了確定使用者是否實際將 `default` 或 `default_factory` 的值傳遞給工廠函數 `fields`。&#x20;

傳遞 `field(default=None)` 是完全有效的； 由於預設值實際上是 `MISSING`，因此`dataclasses` 能夠檢測到此參數已經傳遞了一個 `None` 值。詳見： [field()](/tech/python-library/dataclasses-shu-ju-class.md#field-1)。
{% endhint %}

{% code title="PYTHON" %}

```python
from dataclasses import dataclass, field
from typing import Optional


@dataclass
class Var:
    get: list[str]
    set: Optional[list[str]] = field(default=None)


print(Var(get=['Title'])) # Output: Var(get=['Title'], set=None)
```

{% endcode %}

* `KW_ONLY` Sentinel value 是一種用於指定 Dataclass 的某些 fields 應為 keyword-only arguments。

對於 Dataclass，這意味著在創建 Dataclass 的實例時，必須使用 keyword syntax 提供這些參數。 當您有許多參數時，這對於增強程式碼可讀性和防止錯誤非常有用。

`KW_ONLY` (pseudo-field) 指示 keyword-only fields 的開始，在此之後定義的任何 fields 都將被視為 keyword-only。 該 pseudo-field 的名稱將被忽略，並且按照慣例，使用 `_`。

{% code title="PYTHON" %}

```python
from dataclasses import dataclass, KW_ONLY

@dataclass
class Student:
    name: str
    _: KW_ONLY  # Pseudo-field to indicate the start of keyword-only fields
    age: int = 20
    course: str = 'Computer Science'
    
# This is OK
student1 = Student('John Doe', age=21, course='Mathematics')

# This is OK - using defaults for keyword-only arguments
student2 = Student('Jane Doe')

student3 = Student('John Doe', 21, 'Mathematics')
# TypeError: Student.__init__() takes 2 positional arguments but 4 were given
```

{% endcode %}

## 參考資料

***

[dataclasses — Data Classes — Python 3.12.0b4 documentation](https://docs.python.org/3.12/library/dataclasses.html)

[PEP 526 – Syntax for Variable Annotations | peps.python.org](https://peps.python.org/pep-0526/)

[PEP 557 – Data Classes | peps.python.org](https://peps.python.org/pep-0557/)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.xiwind-corp.com/tech/python-library/dataclasses-shu-ju-class/make_dataclass-replace-is_dataclass-__post_init__.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
