簡介
dataclasses
module 用於向用戶定義的 Class 新增 special methods (特殊方法),例如:__init__
或 __repr__
。
使用 Dataclasses 的主要優點是在建立 Class 時減少 Boilerplate code ,特別是那些主要儲存數據,但沒有太多行為 (方法) 的 Class。
Dataclasses 自動向 Class 加入 special methods,包括:__init__
、__repr__
和 __eq__
。 這些 special methods 手動編寫通常很乏味,特別是對於具有許多屬性的 Class。如果需要,也可以自定義和微調這些方法的行為。
數據類的裝飾器是 @dataclasses.dataclass
。 這是一個簡單的例子:
Copy 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
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 中定義,並且可以指定它們的類型和預設值。
Copy import dataclasses
@dataclasses.dataclass
class Person:
name: str
age: int = 0
在此範例中,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 變數。
Copy @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)
是否將生成 __init__
方法。
如果 Class 已經定義了 __init__
,則忽略此參數。
是否將生成 __repr__
方法。
生成的 repr String 將包含 Class 名稱以及每個 field 的名稱,按照它們在 Class 中定義的順序排列。 不包括標記為從 repr 中排除的 fields 。如果 Class 已經定義了 __repr__
,則忽略此參數。
是否將生成 __eq__
方法。
此方法按順序比較 Class,僅當它們對應的 fields 相等時,該 Class 的兩個實例才會被比較為相等。如果 Class 已經定義了 __eq__
,則忽略此參數。
是否將生成 __lt__
(小於)、__le__
(小於或等於)、__gt__
(大於) 和 __ge__
(大於或等於) 方法。
這些使得 Class 的實例可排序且具有可比性。如果 field's type 不支援比較,這將引發 TypeError。如果 Class 已經定義了 __lt__
、__le__
、__gt__
和 __ge__
中的任何一個,則會引發 TypeError。
如果為 True
, 會強制生成 __hash__
方法,但不建議這樣做,因為它違反了 Python's object model,其中 mutable 物件是 unhashable。
如果為 False
,則根據 eq
和 frozen
的設置方式生成 __hash__
方法。
在實例建立後,指派 fields 將會引發 Exception。
類似於其他語言中的唯讀 (凍結;frozen) 數據類型,就像 Tuple 一樣。如果 Class 中定義了 __setattr__
或 __delattr__
,則會引發 TypeError。
您不能在 Class 中同時擁有顯式 __hash__
方法並設置 unsafe_hash=True
;這將導致 TypeError。
範例 – unsafe_hash (參數)
Copy 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'
field()
該函數用於自定義 Data class 中的各個 fields。就像指定了預設值本身一樣。
Copy field(*,
default=MISSING,
default_factory=MISSING,
init=True,
repr=True,
hash=None,
compare=True,
metadata=None,
kw_only=MISSING)
為 field 提供預設值的零參數函數,當該 field 需要預設值時將調用。 如果同時指定了 default
和 default_factory
,則會引發 TypeError
。
Field 將作為參數包含在生成的 __init__
方法中。
Field 將包含在生成的 __repr__
方法,返回的 String 中。
如果為 True
,則該 field 包含在生成的 __hash__
方法中。
如果為 None (預設值),則使用 compare
Field 將包含在生成的 __eq__
, __gt__
... 方法中。
範例 – 基礎
Data class Employee
有兩個 fields name
和 age
,這兩個 fields 都具有使用 dataclasses.field()
指定的預設值。
Copy 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)
default_factory 參數: 當您想要生成每次都應該是新物件的預設值時,default_factory
非常有用。(例如:空 List 或當前時間戳)
Copy 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!')
repr 參數: color
field 從 __init__
, __repr__
方法中排除。因此在建立 Point
實例時不需要提供它,在打印 Point
物件時不包括它。
Copy 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)
範例 – Mutable 物件
如果 field 是 mutable (例如: List, Dictionary),並且您為其提供預設值,則應該使用 default_factory
來提供一個函數,該函數在每次創建新實例時生成新值。 這避免了在多個實例之間共享相同的可變的值。
Copy 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
fields()
fields(class_or_instance)
獲取為特定 Dataclasses 或 Dataclass 實例定義的每個 Field
物件的 metadata。
透過使用 fields()
,我們可以對程式碼中的 Dataclasses 進行更詳細的控制和理解。 這對於大量使用 Dataclasses 的複雜程序或 libraries 特別有用。
Copy from dataclasses import dataclass, fields
@dataclass
class Point:
x: int
y: int
print(fields(Point))
執行結果:
Copy (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))
範例 – 基礎
Copy 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}')
執行結果:
Copy 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)
Copy from dataclasses import dataclass, asdict
@dataclass
class Point:
x: int
y: int
p = Point(10, 20)
print(asdict(p)) # Outputs: {'x': 10, 'y': 20}
範例 – 具有複雜 field 類型的 Dataclass
Copy 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))
執行結果:
Copy Output: {'name': 'John',
'address': {'city': 'NYC', 'country': 'USA'}}
collections.OrderedDict
保證 fields 的顯示順序與 Dataclass 中定義的順序相同。
Copy 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))
執行結果:
Copy {'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)
。
Copy 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']
astuple()
astuple(obj, *, tuple_factory=tuple)
每個 Dataclass fields 都轉換為 Tuple。透過 dict_factory
,將 Dataclass obj
轉換為 tuple
。
Copy 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))