簡介
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。
生成的方法按照 fields 在 Class 中定義的順序使用 fields。
例如: __init__
方法中的參數順序遵循 Class 中 field 定義的順序。
以下是管理 __hash__
方法隱式建立的規則。
如果 frozen=True, eq=True
,則將生成 __hash__
方法。
如果 frozen=False, eq=True
,則 __hash__
方法將被設置為 None,將其標記為 unhashable。
如果 frozen=False, eq=False
,則 __hash__
將保持不變,這意味著將使用 Superclass 的 __hash__
方法 (如果 Superclass 是 object
,將回退到 id-based hashing)。
默認情況下,dataclass() 不會隱式生成 __hash__
方法 (無法對實例使用 hash()
)。 它也不會更改現有的顯式定義的 __hash__
方法。
您不能在 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__
... 方法中。
MISSING
是一個 Sentinel value 物件,用於檢測使用者是否提供了某些參數。
使用此標記是因為 None 對於某些具有不同含義的參數來說是有效值。 任何程式碼都不應直接使用 MISSING
。
範例 – 基礎
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 特別有用。
雖然 fields()
函數提供有關 Dataclass 的有用訊息,但您應該謹慎使用它們,因為訪問 metadata 可能會減慢您的程序速度。 大多數時候,您只需要直接訪問 fields 的值。
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))