一文帶你走進Python中的數據類
數據類適用于Python3.7或更高版本,它不僅可以用作數據容器,還可以編寫樣板代碼,簡化創建類的過程。

創建第一個數據類
創建一個數據類,該數據類表示三維坐標系中的一個點。
@dataclass裝飾器用于創建數據類。x,y和z是數據類中的字段。注意要使用類型注釋來指定字段的數據類型,但是類型注釋不是靜態類型聲明,這意味著仍然可以為x,y或z字段傳遞除int之外的任何數據類型。
- from dataclasses import dataclass
- @dataclass
- classCoordinate:
- x: int
- y: int
- z: int
默認情況下,數據類附帶有init、repr和 eq方法,因此我們不必自己實現。但是如果init、repr和eq沒有在Coordinate類中實現,有了數據類,我們仍然可以使用這些方法,這樣非常節省時間。
- from dataclasses import dataclass
- @dataclass
- classCoordinate:
- x: int
- y: int
- z: int
- a =Coordinate(4, 5, 3)
- print(a) # output: Coordinate(x=4, y=5, z=3)
字段的默認值
編碼者可以為字段分配默認值。如下所示,數據類中的pi字段被分配了默認值:
- from dataclasses import dataclass
- @dataclass
- classCircleArea:
- r: int
- pi: float =3.14
- @property
- defarea(self):
- return self.pi * (self.r **2)
- a =CircleArea(2)
- print(repr(a)) # output: CircleArea(r=2, pi=3.14)
- print(a.area) # output: 12.56
自定義字段和數據類
設置dataclass裝飾器或field函數的參數可以自定義字段和數據類。自定義過程將用例子進行說明,本文結尾也會給出字段和數據類的所有參數。
數據類可變還是不可變?
默認情況下,數據類是可變的,這意味著可以為字段分配值。但我們可以通過將frozen參數設置為True來使其不可變
可變示例:
- from dataclasses import dataclass
- @dataclass
- classCircleArea:
- r: int
- pi: float =3.14
- @property
- defarea(self):
- return self.pi * (self.r **2)
- a =CircleArea(2)
- a.r =5
- print(repr(a)) # output: CircleArea(r=5, pi=3.14)
- print(a.area) # output: 78.5
不可變示例:
設置frozen為 True,將無法再為字段分配值。在下面的示例中可以看到異常輸出。
- from dataclasses import dataclass
- @dataclass(frozen=True)
- classCircleArea:
- r: int
- pi: float =3.14
- @property
- defarea(self):
- return self.pi * (self.r **2)
- a =CircleArea(2)
- a.r =5
- # Exceptionoccurred: dataclasses.FrozenInstanceError:
- # cannot assign tofield 'r'
比較數據類
假設要創建一個表示Vector的數據類并進行比較,你會怎么做?當然需要使用諸如lt或gt之類的方法啦。
默認情況下,數據類的order參數為 False。將其設置為True,會自動為數據類生成 lt、le、gt和ge方法。因此,可以按順序比較對象,就像它們是其字段的元組一樣。
研究下面的示例:將order設置為True就可以比較v2和v1。這里存在一個邏輯比較的問題。當v2> v1時,它將比較這兩個向量,例如(8,15)>(7,20)。因此,v2> v1的輸出將為True。
回想一下,元組比較是逐個按照順序進行的。首先將8和7進行比較,結果為True,那么比較結果就為True。如果它們相等,則比較15> 20,結果為False:
- from dataclasses import dataclass,field
- @dataclass(order=True)
- classVector:
- x: int
- y: int
- v1 =Vector(8, 15)
- v2 =Vector(7, 20)
- print(v2 > v1)
顯然這種比較沒有任何意義。筆者最初想通過向量的大小來比較它們。但問題是,不可能在創建每個實例時,都要自己計算Vector的大小。
在這種情況下,field函數和post_init方法更有用。field函數能自定義magnitude字段。而post_init方法則會確定初始化后該矢量的大小。
還可以使用數據類中的field函數來自定義magnitude字段。通過將init設置為False,基本可以不需要init方法中的magnitude參數。因為初始化后才使用post_init方法來確定其值:
- from dataclasses import dataclass, field
- @dataclass(order=True)
- classVector:
- magnitude: float =field(init=False)
- x: int
- y: int
- def__post_init__(self):
- self.magnitude = (self.x **2+ self.y **2) **0.5
- v1 =Vector(9, 12)
- print(v1) # output: Vector(magnitude=15.0, x=9,y=12)
- v2 =Vector(8, 15)
- print(v2) # output: Vector(magnitude=17.0, x=8,y=15)
- print(v2 > v1) # output: True
將數據類轉換為字典或元組
從元組或字典中獲取數據類的屬性,只需要從數據類中導入asdict和astuple函數:
- from dataclasses import dataclass,asdict, astuple
- @dataclass
- classVector:
- x: int
- y: int
- z: int
- v =Vector(4, 5, 7)
- print(asdict(v)) # output: {'x': 4, 'y': 5, 'z': 7}
- print(astuple(v)) # output: (4, 5, 7)
繼承
可以像Python中的普通類一樣對數據類進行子類化:
- from dataclasses import dataclass
- @dataclass
- classEmployee:
- name: str
- lang: str
- @dataclass
- classDeveloper(Employee):
- salary: int
- Halil=Developer('Halil', 'Python', 5000)
- print(Halil) # Output: Developer(name='Halil',lang='Python', salary=5000)
使用繼承時經常會忽視一點:默認情況下,當將lang字段設置為Python時,必須為lang字段之后的字段提供默認值:
- from dataclasses import dataclass
- @dataclass
- classEmployee:
- name: str
- lang: str ='Python'
- @dataclass
- classDeveloper(Employee):
- salary: int
- Halil=Developer('Halil', 'Python', 5000)
- # Output:TypeError: non-default argument 'salary' follows default argument
原因在于init方法。回想一下,具有默認值的參數應該位于沒有默認值的參數之后:
- def__init__(name: str,lang: str ='Python', salary: int):
- ...
通過對sanlary字段設置默認值來對其進行修復:
- from dataclasses import dataclass
- @dataclass
- classEmployee:
- name: str
- lang: str ='Python'
- @dataclass
- classDeveloper(Employee):
- salary: int =0
- Halil=Developer('Halil', 'Python', 5000)
- print(Halil) # output: Developer(name='Halil',lang='Python', salary=5000)
slots的好處
默認情況下,屬性存儲在字典中。使用slots可以更快地訪問屬性并且內存占用更少。
- from dataclasses import dataclass
- @dataclass
- classEmployee:
- name: str
- lang: str
- Halil=Employee('Halil', 'Python')
- print(Halil.__dict__) # name': 'Halil', 'lang': 'Python'}
slots內存占用更小,訪問屬性更快。
- from dataclasses import dataclass
- @dataclass
- classEmployee:
- __slots__ = ('name', 'lang')
- name: str
- lang: str
- Halil=Employee('Halil', 'Python')
數據類參數
剛剛我們更改了數據類裝飾器中的某些參數,以自定義數據類。以下是參數列表:
- nit:如果為True,則在數據類中生成init方法。(默認為True)
- repr:如果為True,則在數據類中生成repr方法。(默認為True)
- eq:如果為True,則在數據類中生成eq方法。(默認為True)
- order:如果為True,則在數據類中生成lt,le,gt和ge方法。(默認為False)
- unsafe_hash:如果為True,則在數據類中生成hash方法。(默認為False)
- frozen:如果為True,則不能給字段分配值。(默認為False。)
注意,如果order為True,eq必須也為True,否則將引發ValueError異常。
字段參數
- init:如果為True,則此字段包含在生成的init方法中。(默認為True)
- repr:如果為True,則此字段包含在生成的repr方法中。(默認為True)
- compare:如果為True,則此字段包含在生成的比較和相等方法中。(默認為True)
- hash:如果為True,則此字段包含在生成的hash方法中。(默認為None)
- default:這是此字段的默認值(如果提供)。
- default_factory:當該字段需要默認值時將調用該參數,此時該參數必須為零階可調用參數對象。
- metadata:可以是映射,也可以為空,為空則將其視為空字典。
以上就是關于Python中數據類的簡要介紹,你掌握了嗎?