XiWind 西風之劍
HomeTechProContactGitHub
  • About
  • Git
    • Windows Terminal、PowerShell 安裝
    • Git 開始使用
    • Branch 入門
    • 合併多個 Commit , 編輯
    • 額外功能
  • deep learning
    • Dilated Convolution
  • Python
    • GIL 【全域直譯器鎖】
    • PyPy 【JIT 編譯器】
    • Decorator 【修飾器】
      • Class Decorators
  • Python library
    • abc 【抽象 Class】
      • ABC, ABCMeta
      • __abstractmethods__, get_cache_token, update_abstractmethods
    • dataclasses 【數據 Class】
      • make_dataclass(), replace(), is_dataclass(), __post_init__
    • enum 【列舉 Class】
      • Flag, auto(), unique, verify()
      • 範例
    • concurrent.futures 【執行緒、程序】
      • Future, Module Functions
    • queue 【佇列、同步】
      • full(), empty(), qsize(), join(), task_done()
    • functools 【可調用物件】
      • ordering、wrapper、partial
      • Overloading
    • heapq 【堆積佇列】
      • heapify(), merge(), nlargest(), nsmallest()
    • time 【時間】
      • time(), monotonic(), perf_counter()...
      • sleep(), 範例...
    • logging 【日誌】
Powered by GitBook
On this page
  • make_dataclass()
  • replace()
  • is_dataclass()
  • __post_init__
  • MISSING, KW_ONLY
  • 參考資料

Was this helpful?

  1. Python library
  2. dataclasses 【數據 Class】

make_dataclass()


創建一個新的 Dataclass。

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

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)
參數
說明

cls_name

Dataclass 名稱。

fields

fields 的定義,是一個 iterable 物件。 fields 的元素可以是 name、(name, type) 或 (name, type, Field)。如果僅提供 name ,則轉變為 (name, typing.Any) (應用 typing.Any)。

bases

為正在建立的 Dataclass 指定多個 Base class。 生成的 Class 將從一個或多個 Base class 繼承。

namespace

向 Class 新增其他方法或屬性。 使用 namespace 中給定的命名空間進行初始化)。您可以提供一個 Dictionary,其中 Keys 是方法或屬性的名稱,Values 是實際的實現。

module

如果定義了module,則 Dataclass 的 __module__ 屬性將設置為該值。 預設情況下,設置為調用者的 module 名稱。

init、repr、eq、order、unsafe_hash、frozen、match_args、kw_only、slots 和 weakref_slot 與它們在 dataclass() 中的含義相同。

當您需要動態創建新 Classes 時,make_dataclass() 會非常有用。 對於靜態定義的 Classes,使用 @dataclass 可以更具可讀性。

bases 和 namespace 參數可以成為動態創建複雜 Dataclasses 的強大工具。 但是,它們也會使您的程式碼更難以理解和 debug。

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

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')

範例 – 基礎


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!

範例 – bases, namespace (參數)


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!

replace()


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

PYTHON
replace(obj, /, **changes)

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

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

新返回的物件是透過調用 dataclass 的 __init__() 方法創建的。 這確保了__post_init__ (如果存在)也會被調用。

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

範例 – init=False


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

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

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

  1. 如果提供了預設值,則保留預設值。

  2. 如果設定了它們,則在 __post_init__ 中進行設定。

  3. 未初始化。(如果以上都不成立)

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)

在這個例子中:

  • 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) 被轉移到新實例,除非它被明確變更。

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)

is_dataclass()


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

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

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

範例:

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

__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 實例,在應用程式的其餘部分使用之前,始終處於有效狀態,從而使其成為強大且自我驗證的資料模型的強大功能。

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'])

MISSING, KW_ONLY


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

fields 的預設值是 default=MISSING,而不是 default=None。

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

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)

  • 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 的名稱將被忽略,並且按照慣例,使用 _。

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

參考資料


Previousdataclasses 【數據 Class】Nextenum 【列舉 Class】

Last updated 1 year ago

Was this helpful?

MISSING 僅提供 default 和 default_factory 設置預設值。

傳遞 field(default=None) 是完全有效的; 由於預設值實際上是 MISSING,因此dataclasses 能夠檢測到此參數已經傳遞了一個 None 值。詳見: 。

Sentinel value
dataclasses — Data Classes — Python 3.12.0b4 documentation
PEP 526 – Syntax for Variable Annotations | peps.python.org
PEP 557 – Data Classes | peps.python.org
field()
Page cover image