
make_dataclass()
創建一個新的 Dataclass。
此函數並不是嚴格必需的,提供此函數只是為了方便。你可以使用 dataclass()
來代替 make_dataclass()
。
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 名稱。
當您需要動態創建新 Classes 時,make_dataclass()
會非常有用。 對於靜態定義的 Classes,使用 @dataclass
可以更具可讀性。
bases
和 namespace
參數可以成為動態創建複雜 Dataclasses 的強大工具。 但是,它們也會使您的程式碼更難以理解和 debug。
如果您熟悉傳統的 Python Class 和繼承,在許多情況下,您最好使用 @dataclass
和傳統的 Class 來定義。
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')
範例 – 基礎
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 (參數)
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。
replace(obj, /, **changes)
當您想要建立新的實例改變某些 fields,但希望保持原始 Dataclass 實例不變時,replace()
函數非常有用。
例如:如果您有一個 Dataclass 實例,表示特定時間點某事物的狀態 ,並且您想要表示一個新狀態。但您不想更改原始實例,而是建立一個表示新狀態的新實例。
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])
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" 物件複製。相反,它們將被設定為就像正在創建一個新物件一樣,這意味著它們將是:
如果提供了預設值,則保留預設值。
如果設定了它們,則在
__post_init__
中進行設定。未初始化。(如果以上都不成立)
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
) 被轉移到新實例,除非它被明確變更。
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)
。
def is_dataclass_instance(obj):
return is_dataclass(obj) and not isinstance(obj, type)
範例:
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__
的用法:
初始化驗證: 在物件初始化後使用
__post_init__
來驗證資料。在範例中,它檢查unit_price
是否為負數,如果是則引發ValueError
。派生屬性: 計算從傳遞給
__init__
方法的屬性派生的屬性。在範例中,total_price
是根據unit_price
和quantity_on_hand
計算得出的。類型檢查: 執行自訂類型檢查。該範例確保
serial_numbers
是一個列表,如果不是,則會引發TypeError
。預設可變性: 當使用可變預設值(例如: Lists, Dictionaries)時,使用
default_factory
。這是因為在使用可變預設值時,預設值在 class 的所有實例之間共享,從而導致潛在的錯誤。副作用: 如果您需要在建立實例後執行一些副作用,
__post_init__
是一個好地方。(例如: logging (日誌記錄)、在某些 registry (註冊表) 中註冊等)Field 初始化: 有時你需要設定不是 fields 的實例變數 (未在 class 中定義)。
__post_init__
可用來設定這些特定於實例的屬性,這些屬性不需要是 fields。附加屬性: 也可以為 dataclass 實例新增未定義為 fields 的附加屬性,在範例中,為
total_price
。Non-Field 預設值: 對於初始化不應作為參數傳遞給
__init__
方法的屬性,您可以在__post_init__
中指派它們,可以使用直接值或基於其他 fields 的計算。
透過使用 __post_init__
,您可以確保 data class 實例,在應用程式的其餘部分使用之前,始終處於有效狀態,從而使其成為強大且自我驗證的資料模型的強大功能。
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
Sentinel value 僅提供default
和default_factory
設置預設值。
我們應該避免在應用程序代碼中使用 MISSING
。MISSING
僅在 dataclasses
module 幕後使用,並發揮它魔法。
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 的名稱將被忽略,並且按照慣例,使用 _
。
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
參考資料
dataclasses — Data Classes — Python 3.12.0b4 documentation
Last updated
Was this helpful?