深入理解Python?@dataclass的內(nèi)部原理
之前寫過一篇介紹Python
中dataclass
的文章:《掌握python的dataclass,讓你的代碼更簡潔優(yōu)雅》。
那篇側重于介紹dataclass
的使用,今天想探索一下這個有趣的特性是如何實現(xiàn)的。
表面上看,dataclass
就是一個普通的裝飾器,但是它又在class
上實現(xiàn)了很多神奇的功能,
為我們在Python
中定義和使用class
帶來了極大的便利。
如果你也好奇它在幕后是如何工作的,本篇我們就一同揭開Python
中dataclass
的神秘面紗,
深入探究一下其內(nèi)部原理。
1. dataclass簡介
dataclass
為我們提供了一種簡潔而高效的方式來定義類,特別是那些主要用于存儲數(shù)據(jù)的類。
它能自動為我們生成一些常用的方法,如__init__
、__repr__
等,大大減少了樣板代碼的編寫。
例如,我在量化中經(jīng)常用的一個K線數(shù)據(jù),用dataclass來定義的話,如下所示:
from dataclasses import dataclass from datetime import datetime @dataclass class KLine: name: str = "BTC" open_price: float = 0.0 close_price: float = 0.0 high_price: float = 0.0 low_price: float = 0.0 begin_time: datetime = datetime.now() if __name__ == "__main__": kl = KLine() print(kl)
這樣,我們無需手動編寫__init__
方法來初始化對象,就可以輕松創(chuàng)建KLine
類的實例,
并且直接打印對象也可以得到清晰,易于閱讀的輸出。
$ python.exe .\kline.py KLine(name='BTC', open_price=0.0, close_price=0.0, high_price=0.0, low_price=0.0, begin_time=datetime.datetime(2025, 1, 2, 17, 45, 53, 44463))
但這背后究竟發(fā)生了什么呢?
2. 核心概念
dataclass
從Python3.7
版本開始,已經(jīng)加入到標準庫中了。
代碼就在Python
安裝目錄中的Lib/dataclasses.py
文件中。
實現(xiàn)這個裝飾器功能的核心有兩個:__annotations__
屬性和exec
函數(shù)。
2.1. __annotations__屬性
__annotations__
是 Python
中一個隱藏的寶藏,它以字典的形式存儲著變量、屬性以及函數(shù)參數(shù)或返回值的類型提示。
對于dataclass
來說,它就像是一張地圖,裝飾器通過它來找到用戶定義的字段。
比如,在上面的KLine
類中,__annotations__
會返回字段的相關信息。
這使得dataclass
裝飾器能夠清楚地知道類中包含哪些字段以及它們的類型,為后續(xù)的操作提供了關鍵信息。
if __name__ == "__main__": print(KLine.__annotations__) # 運行結果: {'name': <class 'str'>, 'open_price': <class 'float'>, 'close_price': <class 'float'>, 'high_price': <class 'float'>, 'low_price': <class 'float'>, 'begin_time': <class 'datetime.datetime'>}
2.2. exec 函數(shù)
exec
函數(shù)堪稱dataclass
實現(xiàn)的魔法棒,它能夠?qū)⒆址问降拇a轉(zhuǎn)換為 Python
對象。
在dataclass
的世界里,它被用來創(chuàng)建各種必要的方法。
我們可以通過構建函數(shù)定義的字符串,然后使用exec
將其轉(zhuǎn)化為真正的函數(shù),并添加到類中。
這就是dataclass
裝飾器能夠自動生成__init__
、__repr__
等方法的秘密所在。
下面的代碼通過exec
,將一個字符串代碼轉(zhuǎn)換成一個真正可使用的函數(shù)。
# 定義一個存儲代碼的字符串 code_string = """ def greet(name): print(f"Hello, {name}!") """ # 使用 exec 函數(shù)執(zhí)行代碼字符串 exec(code_string) # 調(diào)用通過 exec 生成的函數(shù) greet("Alice")
3. 自定義dataclass裝飾器
掌握了上面的核心概念,我們就可以開始嘗試實現(xiàn)自己的dataclass
裝飾器。
當然,這里只是簡單實現(xiàn)個雛形,目的是為了了解Python
標準庫中dataclass
的原理。
下面主要實現(xiàn)兩個功能__init__
和__repr__
。
通過這兩個功能來理解dataclass
的實現(xiàn)原理。
3.1. 定義架構
我們首先定義一個dataclass
裝飾器,它的結構如下:
def dataclass(cls=None, init=True, repr=True): def wrap(cls): # 這里將對類進行修改 return cls if cls is None: return wrap return wrap(cls)
接下來,我們在這個裝飾器中實現(xiàn)__init__
和__repr__
。
3.2. 初始化:init
當init
參數(shù)為True
時,我們?yōu)轭愄砑?code>__init__方法。
通過_init_fn
函數(shù)來實現(xiàn),它會根據(jù)類的字段生成__init__
方法的函數(shù)定義字符串,然后使用_create_fn
函數(shù)將其轉(zhuǎn)換為真正的方法并添加到類中。
def _create_fn(cls, name, fn): ns = {} exec(fn, None, ns) method = ns[name] setattr(cls, name, method) def _init_fn(cls, fields): args = ", ".join(fields) lines = [f"self.{field} = {field}" for field in fields] body = "\n".join(f" {line}" for line in lines) txt = f"def __init__(self, {args}):\n{body}" _create_fn(cls, "__init__", txt)
3.3. 美化輸出:repr
__repr__
方法讓我們能夠以一種清晰易讀的方式打印出類的實例。
為了實現(xiàn)這個功能,我們創(chuàng)建_repr_fn
函數(shù),它生成__repr__
方法的定義字符串。
這個方法會獲取實例的__dict__
屬性中的所有變量,并使用 f-string
進行格式化輸出。
def _repr_fn(cls, fields): txt = ( "def __repr__(self):\n" " fields = [f'{key}={val!r}' for key, val in self.__dict__.items()]\n" " return f'{self.__class__.__name__}({\"\\n \".join(fields)})'" ) _create_fn(cls, "__repr__", txt)
3.4. 合在一起
最終的代碼如下,代碼中使用的是自己的dataclass
裝飾器,而不是標準庫中的dataclass
。
from datetime import datetime def dataclass(cls=None, init=True, repr=True): def wrap(cls): fields = cls.__annotations__.keys() if init: _init_fn(cls, fields) if repr: _repr_fn(cls, fields) return cls if cls is None: # 如果裝飾器帶參數(shù) return wrap return wrap(cls) def _create_fn(cls, name, fn): ns = {} exec(fn, None, ns) method = ns[name] setattr(cls, name, method) def _init_fn(cls, fields): args = ", ".join(fields) lines = [f"self.{field} = {field}" for field in fields] body = "\n".join(f" {line}" for line in lines) txt = f"def __init__(self, {args}):\n{body}" _create_fn(cls, "__init__", txt) def _repr_fn(cls, fields): txt = ( "def __repr__(self):\n" " fields = [f'{key}={val!r}' for key, val in self.__dict__.items()]\n" " return f'{self.__class__.__name__}({\"\\n \".join(fields)})'" ) _create_fn(cls, "__repr__", txt) @dataclass class KLine: name: str = "BTC" open_price: float = 0.0 close_price: float = 0.0 high_price: float = 0.0 low_price: float = 0.0 begin_time: datetime = datetime.now() if __name__ == "__main__": kl = KLine( name="ETH", open_price=1000.5, close_price=3200.5, high_price=3400, low_price=200, begin_time=datetime.now(), ) print(kl)
運行的效果如下:
可以看出,我們自己實現(xiàn)的dataclass
裝飾器也可以實現(xiàn)類的初始化和美化輸出,這里輸出時每個屬性占一行。
4. 總結
通過自定義dataclass
裝飾器的構建過程,我們深入了解了 Python
中dataclass
的內(nèi)部原理。
利用__annotations__
獲取字段信息,借助exec
創(chuàng)建各種方法,從而實現(xiàn)簡潔高效的dataclass
定義。
不過,實際的 Python
標準庫中的dataclass
還有更多的功能和優(yōu)化,了解了其原理之后,可以參考它的源碼再進一步學習。
到此這篇關于探索Python @dataclass的內(nèi)部原理的文章就介紹到這了,更多相關Python @dataclass原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
python自定義函數(shù)中的return和print使用及說明
這篇文章主要介紹了python自定義函數(shù)中的return和print使用及說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-01-01Python實現(xiàn)信息轟炸工具(再也不怕說不過別人了)
不知道各位小伙伴有沒有遇到過這樣的一個故事,發(fā)現(xiàn)自己直接噴不過,打字速度不夠給力.下面這篇文章就能解決自己噴不過的苦惱,話不多說,上才藝,需要的朋友可以參考下2021-06-06解決Keyerror ''''acc'''' KeyError: ''''val_acc''''問題
這篇文章主要介紹了解決Keyerror 'acc' KeyError: 'val_acc'問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-06-06Python簡單實現(xiàn)TCP包發(fā)送十六進制數(shù)據(jù)的方法
這篇文章主要介紹了Python簡單實現(xiàn)TCP包發(fā)送十六進制數(shù)據(jù)的方法,結合實例形式簡單分析了Python實現(xiàn)TCP數(shù)據(jù)傳輸及發(fā)送十六進制數(shù)據(jù)包的相關技巧,需要的朋友可以參考下2016-04-04python Elasticsearch索引建立和數(shù)據(jù)的上傳詳解
在本篇文章里小編給大家整理的是關于基于python的Elasticsearch索引的建立和數(shù)據(jù)的上傳的知識點內(nèi)容,需要的朋友們參考下。2019-08-08Python使用Selenium模塊模擬瀏覽器抓取斗魚直播間信息示例
這篇文章主要介紹了Python使用Selenium模塊模擬瀏覽器抓取斗魚直播間信息,涉及Python基于Selenium模塊的模擬瀏覽器登陸、解析、抓取信息,以及MongoDB數(shù)據(jù)庫的連接、寫入等相關操作技巧,需要的朋友可以參考下2018-07-07Python實現(xiàn)鏈表反轉(zhuǎn)與合并操作詳解
這篇文章主要為大家詳細介紹了?Python?中反轉(zhuǎn)鏈表和合并鏈表的應用場景及實現(xiàn)方法,文中的示例代碼講解詳細,感興趣的小伙伴可以了解下2025-02-02