Python中的數(shù)據(jù)類dataclass解讀
1. 為什么需要數(shù)據(jù)類

1.1 ??內(nèi)置數(shù)據(jù)類型的局限
假設(shè)我們現(xiàn)在遇到一個場景, 需要一個數(shù)據(jù)對象來保存一些運動員信息.
可以選擇使用基本的數(shù)據(jù)類型tuple或者dict實現(xiàn). 如:創(chuàng)建一個球員jordan, 信息包括球員姓名, 號碼, 位置, 年齡.
使用tuple
In [1]: jordan = ('Micheal Jordan', 23, 'PG', 29)
In [2]: jordan[0]
Out[2]: 'Micheal Jordan'劣勢:創(chuàng)建和取值基于位置, 需要記住坐標對應(yīng)的信息.
使用dict
In [3]: jordan = {'name': 'Micheal Jordan', 'number': 23, 'position': 'PG', 'age': 29}
In [4]: jordan['position']
Out[4]: 'PG'使用字典之后, 獲取信息時會相對直觀, 但是相較于字典的括號語法 jordan["position"] 我們更希望可以用類似獲取屬性一樣使用 jordan.postion.
劣勢:無法對數(shù)據(jù)屬性名進行控制,
少值或者錯值,如, jordan = {'name': 'Micheal Jordan', 'NUMBER': 23} 一樣可以創(chuàng)建成功.
1.2 ??使用命名元組 namedtuple
為了解決這種問題, python 中的 collections 模塊提供一個命名元組, 可以使用點表示法和字段名稱訪問給定命名元組中的值.
使用namedtuple代碼如下
In [5]: from collections import namedtuple
In [6]: Player = namedtuple('Player', ['name', 'number', 'position', 'age', 'grade'])
In [7]: jordan = Player('Micheal Jordan', 23, 'PG', 29, 'S+')
In [8]: jordan
Out[8]: Player(name='Micheal Jordan', number=23, position='PG', age=29, grade='S+')使用namedtuple之后
① 可以使用 '.'語法獲取數(shù)據(jù)的屬性, 可以限制數(shù)據(jù)的屬性名稱,
② 創(chuàng)建對象時數(shù)據(jù)不匹配會報錯.
In [9]: jordan.number
Out[9]: 23
In [10]: bryant = Player('Kobe Bryant', 24, 'PG')
---------------------------------------------------------------------------
TypeError: __new__() missing 2 required positional arguments: 'age' and 'grade'1.2.1 namedtuple的不足
對于一些字段比較少的數(shù)據(jù)結(jié)構(gòu), namedtuple是一個非常好的解決方案.
但面對一些復(fù)雜的數(shù)據(jù)的時候, 需要更多的功能時, namedtuple就無法滿足了.
In [11]: bryant = Player('Kobe Bryant', 24, 'PG', 22, 'S')
In [12]: bryant.age=23
---------------------------------------------------------------------------
AttributeError: can't set attribute劣勢:
① 數(shù)據(jù)無法修改
② 無法自定義數(shù)據(jù)比較, 沒有默認值, 沒有函數(shù)支持.
1.3 自定義類 Class
為了支持數(shù)據(jù)修改, 默認值, 比較等功能. 更加好一些的方法是, 使用自定義類來實現(xiàn)數(shù)據(jù)類.
一個最簡單的數(shù)據(jù)類代碼如下:
In [13]: class Player:
...: def __init__(self, name, number, position, age, grade):
...: self.name = name
...: self.number = number
...: self.position = position
...: self.age = age
...: self.grade = grade
In [14]: bryant = Player(name='Kobe Bryant', number=24, position='PG', age=22, grade='S')
In [15]: jordan = Player('Micheal Jordan', 23, 'PG', 29, 'S+')可以使用位置參數(shù)或者鍵值參數(shù)創(chuàng)建對象
In [16]: bryant.position='SF' In [17]: bryant.position Out[17]: 'SF'
可以看到, 數(shù)據(jù)類可以支持對屬性的修改,
In [18]: bryant Out[18]: <__main__.Player at 0x29446d401c8>
問題①:目前的實現(xiàn) 對于對象的描述不太友好,
In [19]: jordan > bryant --------------------------------------------------------------------------- TypeError: '>' not supported between instances of 'Player' and 'Player'
問題②:數(shù)據(jù)還不支持比較.
為了解決上面兩個問題,可以通過實現(xiàn) __repr__ 方法來自定義描述, 實現(xiàn) __gt__ 方法來支持比較的功能.
更新代碼如下:
In [20]: class Player:
...: def __init__(self, name, number, position, age, grade):
...: self.name = name
...: self.number = number
...: self.position = position
...: self.age = age
...: self.grade = grade
...: def __repr__(self):
...: return f'Player: \n {self.name}\t #{self.number}\t @{self.position}\t <{self.grade}>'
...: def __eq__(self, other):
...: return self.age == other.age
...: def __gt__(self, other):
...: return self.age > other.age
...: def swing(self, pos):
...: self.position = pos
In [21]: jordan = Player('Micheal Jordan', 23, 'PG', 29, 'S+')
In [22]: bryant = Player('Kobe Bryant', 24, 'PG', 22, 'S')
In [23]: jordan
Out[23]:
Player:
Micheal Jordan #23 @PG <S+>
In [24]: jordan > bryant
Out[24]: True
In [25]: jordan.swing('SF')
In [26]: jordan
Out[26]:
Player:
Micheal Jordan #23 @SF <S+>可以看到數(shù)據(jù)對象有了更直觀的描述, 支持了對比 (若要支持 >= 的對比, 還需要自定義 __ge__方法). 還可以自定義方法swing來改變球員打的位置.
劣勢:
① __init__方法中重復(fù)代碼 (示例中每個屬性都需要寫3遍)
② 需要自己實現(xiàn)__repr__方法, 和比較方法__eq__, __gt__等
1.4 ??數(shù)據(jù)類 dataclass
主角出場了, 數(shù)據(jù)類是Python3.7 開始引入的一個新功能, 數(shù)據(jù)類提供了開箱即用的方法來創(chuàng)建自定義數(shù)據(jù), 可以直接實例化、打印和比較數(shù)據(jù)類實例.
In [1]: from dataclasses import dataclass
In [2]: @dataclass
...: class Player:
...: name: str
...: number: int
...: position: str
...: age: int
...: grade: str
In [3]: james = Player('Lebron James', 23, 'SF', 25, 'S')
In [4]: james
Out[4]: Player(name='Lebron James', number=23, position='SF', age=25, grade='S')2. dataclass 的使用
2.1 類型提示和默認值
dataclass 可以認為是提供了一個簡寫__init__方法的語法糖.
類型注釋是必填項 (不限制數(shù)據(jù)類型時, 添加typing.Any為類型注釋), 默認值的傳遞方式和__init__方法的參數(shù)格式一致.
In [1]: from dataclasses import dataclass In [2]: from typing import Any In [3]: @dataclass ...: class Data: ...: name: Any ...: value: Any = 42
2.2 數(shù)據(jù)嵌套
數(shù)據(jù)類可以嵌套為其他數(shù)據(jù)類的字段, 可以簡單創(chuàng)建一個有2個隊員的球隊.lal包含兩名球員 james和davis
In [1]: from dataclasses import dataclass
In [2]: from typing import List
In [3]: @dataclass
...: class Player:
...: name: str
...: number: int
...: position: str
...: age: int
...: grade: str
In [4]: @dataclass
...: class Team:
...: name: str
...: players: List[Player]
In [5]: james = Player('Lebron James', 23, 'SF', 25, 'S')
In [6]: davis = Player('Anthony Davis', 3, 'PF', 21, 'S-')
In [7]: lal = Team('Los Angeles Lakers', [james, davis])
In [8]: lal
Out[8]: Team(name='Los Angeles Lakers', players=[Player(name='Lebron James', number=23, position='SF', age=25, grade='S'), Player(name='Anthony Davis', number=3, position='PF', age=21, grade='S-')])2.3 dataclasses中的field
當我們嘗試使用可變的數(shù)據(jù)類型, 給數(shù)據(jù)類中做默認值時, 觸發(fā)了python中的大坑之一 使用可變默認參數(shù), 導(dǎo)致多個實例公用一個數(shù)據(jù)從而引發(fā)bug.
dataclass 默認阻止使用可變數(shù)據(jù)做默認值
In [9]: @dataclass ...: class Team: ...: name: str ...: players: List[Player] = [james] --------------------------------------------------------------------------- ValueError: mutable default <class 'list'> for field players is not allowed: use default_factory
就像錯誤提示中的, 處理此種場景時, 需要使用 field 中的 default_factory .
In [10]: from dataclasses import field
In [11]: @dataclass
...: class Team:
...: name: str
...: players: List[Player] = field(default_factory=lambda :[james])
In [12]: nyk = Team('New York Knicks')
In [13]: nyk
Out[13]: Team(name='New York Knicks', players=[Player(name='Lebron James', number=23, position='SF', age=25, grade='S')])| 參數(shù) | 描述 | 默認值 |
| default | 字段的默認值 | |
| default_factory | 返回字段初始值的函數(shù) | |
| init | 是否在.__init__()方法中使用字段 | True |
| repr | 是否在.__repr__()方法中使用字段 | True |
| compare | 是否在比較對象時, 包括該字段 | True |
| hash | 計算hash時, 是否包括字段 | True |
| metadata | 包含字段信息的映射 |
2.4 不可變數(shù)據(jù)類
要使數(shù)據(jù)類不可變,需要在創(chuàng)建類時設(shè)置frozen=True。
In [1]: from dataclasses import dataclass
In [2]: from typing import Any
In [3]: @dataclass(frozen=True)
...: class Data:
...: name: Any
...: value: Any = 42
In [4]: data = Data('myname', 99)
In [4]: data.name = 'other'
---------------------------------------------------------------------------
FrozenInstanceError: cannot assign to field 'name'
總結(jié)
dataclass 提供一個簡便的方式創(chuàng)建數(shù)據(jù)類, 默認實現(xiàn)__init__(), __repr__(), __eq__()方法.
dataclass支持數(shù)據(jù)類型的嵌套
支持將數(shù)據(jù)設(shè)置為不可變
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
pyinstaller打包exe程序的步驟和添加依賴文件的實現(xiàn)
這篇文章主要介紹了pyinstaller打包exe程序的步驟和添加依賴文件的實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02
解決編碼問題:UnicodeDecodeError: 'utf-8' codec
這篇文章主要介紹了快速解決編碼問題:UnicodeDecodeError: 'utf-8' codec can't decod,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-05-05
Python系統(tǒng)監(jiān)控之跨平臺檢查系統(tǒng)服務(wù)運行狀態(tài)的完整指南
系統(tǒng)服務(wù)是操作系統(tǒng)和應(yīng)用程序正常運行的基礎(chǔ),本文將和大家分享一個系統(tǒng)服務(wù)狀態(tài)檢查的Python實用腳本,可以跨平臺檢查系統(tǒng)服務(wù)的運行狀態(tài),并提供詳細的報告和告警功能,有需要的可以參考下2025-12-12

