掌握python的dataclass,讓你的代碼更簡潔優雅

dataclass是從Python3.7版本開始,作爲標準庫中的模塊被引入。
隨着Python版本的不斷更新,dataclass也逐步發展和完善,爲Python開發者提供了更加便捷的數據類創建和管理方式。

dataclass的主要功能在於幫助我們簡化數據類的定義過程。
本文總結了幾個我平時使用較多dataclass技巧。

1. 傳統的類定義方式

首先,從平時量化分析的場景中簡化一個關於 幣交易 的類用來演示。
簡化之後,這裏只保留5個字段,分別是交易ID交易對價格是否成功參與交易的地址列表

class CoinTrans:
    def __init__(
        self,
        id: str,
        symbol: str,
        price: float,
        is_success: bool,
        addrs: list,
    ) -> None:
        self.id = id
        self.symbol = symbol
        self.price = price
        self.addrs = addrs
        self.is_success = is_success

Python傳統定義類的方式,如上通過__init__函數來初始化對象的各個屬性。

通過這個類構造對象並打印:

if __name__ == "__main__":
    coin_trans = CoinTrans("id01", "BTC/USDT", "71000", True, ["0x1111", "0x2222"])
    print(coin_trans)

運行結果:

<__main__.CoinTrans object at 0x0000022A891FADD0>

這裏只是打印出對象的地址,並沒有按照我們期望的那樣打印對象各個屬性的值。

傳統的類中,我們如果希望打印出可讀的結果,需要自己去實現__str__函數。

# 在上面的 CoinTrans 類中添加下面的方法
def __str__(self) -> str:
    return f"交易信息:{self.id}, {self.symbol}, {self.price}, {self.addrs}, {self.is_success}"

再次運行,結果如下:

交易信息:id01, BTC/USDT, 71000, ['0x1111', '0x2222'], True

2. dataclass裝飾器定義類

下面看看使用dataclass裝飾器來定義上面同樣的類有多簡單。

from dataclasses import dataclass

@dataclass
class CoinTrans:
    id: str
    symbol: str
    price: float
    is_success: bool
    addrs: list

再次運行:

if __name__ == "__main__":
    coin_trans = CoinTrans("id01", "BTC/USDT", "71000", True, ["0x1111", "0x2222"])
    print(coin_trans)

得到如下結果:

CoinTrans(id='id01', symbol='BTC/USDT', price='71000', is_success=True, addrs=['0x1111', '0x2222'])

不需要__init__,也不需要__str__,只要通過 @dataclass裝飾之後,就可以打印出對象的具體內容。

2.1. 默認值

dataclass裝飾器的方式來定義類,設置默認值很簡單,直接在定義屬性時就可以設置。

@dataclass
class CoinTrans:
    id: str = "id01"
    symbol: str = "BTC/USDT"
    price: float = "71000.8"
    is_success: bool = True
    addrs: list[str] = ["0x1111", "0x2222"]

if __name__ == "__main__":
    coin_trans = CoinTrans()
    print(coin_trans)

運行之後發現,在addrs屬性那行會報錯:

ValueError: mutable default <class 'list'> for field addrs is not allowed: use default_factory

大概的意思就是,list作爲一種可變的類型(引用類型,會有被其他對象意外修改的風險),不能直接作爲默認值,需要用工廠方法來產生默認值。
其他字符串,數值,布爾類型的數據則沒有這個問題。

我們只要定義個函數來產生此默認值即可。

def gen_list():
    return ["0x1111", "0x2222"]

@dataclass
class CoinTrans:
    id: str = "id01"
    symbol: str = "BTC/USDT"
    price: float = "71000.8"
    is_success: bool = True
    addrs: list[str] = field(default_factory=gen_list)

if __name__ == "__main__":
    coin_trans = CoinTrans()
    print(coin_trans)

再次運行,可以正常執行:

CoinTrans(id='id01', symbol='BTC/USDT', price='71000.8', is_success=True, addrs=['0x1111', '0x2222']

2.2. 隱藏敏感信息

我們打印對象信息的時候,有時執行打印其中幾個屬性的信息,涉及敏感信息的屬性不希望打印出來。
比如,上面的對象,如果不想打印出is_successaddrs的信息,可以設置repr=False

@dataclass
class CoinTrans:
    id: str = "id01"
    symbol: str = "BTC/USDT"
    price: float = "71000.8"
    is_success: bool = field(default=True, repr=False)
    addrs: list[str] = field(default_factory=gen_list, repr=False)

再次運行後顯示:

CoinTrans(id='id01', symbol='BTC/USDT', price='71000.8')

2.3. 只讀對象

數據分析時,大部分下情況下,原始數據讀取之後是不能修改的。
這種情況下,我們可以用dataclassfrozen屬性來設置數據類只讀,防止不小心篡改了數據。

未設置frozen屬性之前,可以隨意修改對象的屬性,比如:

if __name__ == "__main__":
    coin_trans = CoinTrans()
    print(f"修改前: {coin_trans}")
    coin_trans.symbol = "ETH/USDT"
    print(f"修改後: {coin_trans}")

運行結果:

修改前: CoinTrans(id='id01', symbol='BTC/USDT', price='71000.8')
修改後: CoinTrans(id='id01', symbol='ETH/USDT', price='71000.8')

設置frozen屬性之後,看看修改屬性值會怎麼樣:

@dataclass(frozen=True)
class CoinTrans:
    id: str = "id01"
    #... 省略 ...

再次運行,會發現修改屬性會觸發異常。

修改前: CoinTrans(id='id01', symbol='BTC/USDT', price='71000.8')
Traceback (most recent call last):
  File "D:\projects\python\samples\data_classes\main.py", line 66, in <module>
    coin_trans.symbol = "ETH/USDT"
    ^^^^^^^^^^^^^^^^^
  File "<string>", line 4, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'symbol'

2.4. 轉化爲元組和字典

最後,dataclasses模塊還提供了兩個函數可以很方便的將數據類轉換爲元組字典
這在和其他分析程序交互時非常有用,因爲和其他程序交互時,參數一般都用元組或者字典這種簡單通用的結構,
而不會直接用自己定義的數據類。

from dataclasses import dataclass, field, astuple, asdict

if __name__ == "__main__":
    coin_trans = CoinTrans()
    print(astuple(coin_trans))
    print(asdict(coin_trans))

運行結果:

('id01', 'BTC/USDT', '71000.8', True, ['0x1111', '0x2222'])
{'id': 'id01', 'symbol': 'BTC/USDT', 'price': '71000.8', 'is_success': True, 'addrs': ['0x1111', '0x2222']}

3. 總結

Python中,數據類主要用於存儲數據,並通常包含屬性和方法來操作這些數據。
然而,在定義數據類時,我們通常需要編寫一些重複性的代碼,如構造函數、屬性訪問器和字符串表示等。
dataclass裝飾器的出現,使得這些通用方法的生成變得自動化,從而極大地簡化了數據類的定義過程。

總的來說,dataclass通過簡化數據類的創建和管理過程,提高了開發效率,是我們在數據分析時的一個非常有用的工具。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章