深入理解Python?@dataclass的內(nèi)部原理
之前寫(xiě)過(guò)一篇介紹Python中dataclass的文章:《掌握python的dataclass,讓你的代碼更簡(jiǎn)潔優(yōu)雅》。
那篇側(cè)重于介紹dataclass的使用,今天想探索一下這個(gè)有趣的特性是如何實(shí)現(xiàn)的。
表面上看,dataclass就是一個(gè)普通的裝飾器,但是它又在class上實(shí)現(xiàn)了很多神奇的功能,
為我們?cè)?code>Python中定義和使用class帶來(lái)了極大的便利。
如果你也好奇它在幕后是如何工作的,本篇我們就一同揭開(kāi)Python中dataclass的神秘面紗,
深入探究一下其內(nèi)部原理。
1. dataclass簡(jiǎn)介
dataclass為我們提供了一種簡(jiǎn)潔而高效的方式來(lái)定義類,特別是那些主要用于存儲(chǔ)數(shù)據(jù)的類。
它能自動(dòng)為我們生成一些常用的方法,如__init__、__repr__等,大大減少了樣板代碼的編寫(xiě)。
例如,我在量化中經(jīng)常用的一個(gè)K線數(shù)據(jù),用dataclass來(lái)定義的話,如下所示:
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)這樣,我們無(wú)需手動(dòng)編寫(xiě)__init__方法來(lái)初始化對(duì)象,就可以輕松創(chuàng)建KLine類的實(shí)例,
并且直接打印對(duì)象也可以得到清晰,易于閱讀的輸出。
$ 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版本開(kāi)始,已經(jīng)加入到標(biāo)準(zhǔn)庫(kù)中了。
代碼就在Python安裝目錄中的Lib/dataclasses.py文件中。
實(shí)現(xiàn)這個(gè)裝飾器功能的核心有兩個(gè):__annotations__屬性和exec函數(shù)。
2.1. __annotations__屬性
__annotations__是 Python 中一個(gè)隱藏的寶藏,它以字典的形式存儲(chǔ)著變量、屬性以及函數(shù)參數(shù)或返回值的類型提示。
對(duì)于dataclass來(lái)說(shuō),它就像是一張地圖,裝飾器通過(guò)它來(lái)找到用戶定義的字段。
比如,在上面的KLine類中,__annotations__會(huì)返回字段的相關(guān)信息。
這使得dataclass裝飾器能夠清楚地知道類中包含哪些字段以及它們的類型,為后續(xù)的操作提供了關(guān)鍵信息。
if __name__ == "__main__":
print(KLine.__annotations__)
# 運(yùn)行結(jié)果:
{'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實(shí)現(xiàn)的魔法棒,它能夠?qū)⒆址问降拇a轉(zhuǎn)換為 Python 對(duì)象。
在dataclass的世界里,它被用來(lái)創(chuàng)建各種必要的方法。
我們可以通過(guò)構(gòu)建函數(shù)定義的字符串,然后使用exec將其轉(zhuǎn)化為真正的函數(shù),并添加到類中。
這就是dataclass裝飾器能夠自動(dòng)生成__init__、__repr__等方法的秘密所在。
下面的代碼通過(guò)exec,將一個(gè)字符串代碼轉(zhuǎn)換成一個(gè)真正可使用的函數(shù)。
# 定義一個(gè)存儲(chǔ)代碼的字符串
code_string = """
def greet(name):
print(f"Hello, {name}!")
"""
# 使用 exec 函數(shù)執(zhí)行代碼字符串
exec(code_string)
# 調(diào)用通過(guò) exec 生成的函數(shù)
greet("Alice")3. 自定義dataclass裝飾器
掌握了上面的核心概念,我們就可以開(kāi)始嘗試實(shí)現(xiàn)自己的dataclass裝飾器。
當(dāng)然,這里只是簡(jiǎn)單實(shí)現(xiàn)個(gè)雛形,目的是為了了解Python標(biāo)準(zhǔn)庫(kù)中dataclass的原理。
下面主要實(shí)現(xiàn)兩個(gè)功能__init__和__repr__。
通過(guò)這兩個(gè)功能來(lái)理解dataclass的實(shí)現(xiàn)原理。
3.1. 定義架構(gòu)
我們首先定義一個(gè)dataclass裝飾器,它的結(jié)構(gòu)如下:
def dataclass(cls=None, init=True, repr=True):
def wrap(cls):
# 這里將對(duì)類進(jìn)行修改
return cls
if cls is None:
return wrap
return wrap(cls)接下來(lái),我們?cè)谶@個(gè)裝飾器中實(shí)現(xiàn)__init__和__repr__。
3.2. 初始化:init
當(dāng)init參數(shù)為True時(shí),我們?yōu)轭愄砑?code>__init__方法。
通過(guò)_init_fn函數(shù)來(lái)實(shí)現(xiàn),它會(huì)根據(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__方法讓我們能夠以一種清晰易讀的方式打印出類的實(shí)例。
為了實(shí)現(xiàn)這個(gè)功能,我們創(chuàng)建_repr_fn函數(shù),它生成__repr__方法的定義字符串。
這個(gè)方法會(huì)獲取實(shí)例的__dict__屬性中的所有變量,并使用 f-string 進(jìn)行格式化輸出。
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裝飾器,而不是標(biāo)準(zhǔn)庫(kù)中的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)運(yùn)行的效果如下:
可以看出,我們自己實(shí)現(xiàn)的dataclass裝飾器也可以實(shí)現(xiàn)類的初始化和美化輸出,這里輸出時(shí)每個(gè)屬性占一行。
4. 總結(jié)
通過(guò)自定義dataclass裝飾器的構(gòu)建過(guò)程,我們深入了解了 Python 中dataclass的內(nèi)部原理。
利用__annotations__獲取字段信息,借助exec創(chuàng)建各種方法,從而實(shí)現(xiàn)簡(jiǎn)潔高效的dataclass定義。
不過(guò),實(shí)際的 Python標(biāo)準(zhǔn)庫(kù)中的dataclass還有更多的功能和優(yōu)化,了解了其原理之后,可以參考它的源碼再進(jìn)一步學(xué)習(xí)。
到此這篇關(guān)于探索Python @dataclass的內(nèi)部原理的文章就介紹到這了,更多相關(guān)Python @dataclass原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
通過(guò)python掃描二維碼/條形碼并打印數(shù)據(jù)
這篇文章主要介紹了通過(guò)python掃描二維碼/條形碼并打印數(shù)據(jù),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
分享2個(gè)方便調(diào)試Python代碼的實(shí)用工具
這篇文章主要介紹了分享方便調(diào)試Python代碼的2個(gè)實(shí)用工具,可以方便展示我們調(diào)試代碼的中間狀態(tài),提升大家的編碼效率,詳細(xì)的介紹需要的小伙伴可以參考一下下面文章內(nèi)容2022-05-05
Python用61行代碼實(shí)現(xiàn)圖片像素化的示例代碼
這篇文章主要介紹了Python用61行代碼實(shí)現(xiàn)圖片像素化的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-12-12
利用python操作SQLite數(shù)據(jù)庫(kù)及文件操作詳解
這篇文章主要給大家介紹了關(guān)于利用python操作SQLite數(shù)據(jù)庫(kù)及文件操作的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09
Python數(shù)據(jù)可視化中常見(jiàn)的4種標(biāo)注方式及示例詳解
在Python的數(shù)據(jù)可視化中,標(biāo)注(Annotation)技術(shù)是一種非常有用的工具,它可以幫助用戶更準(zhǔn)確地解釋圖表中的數(shù)據(jù)和模式,在本文中,將帶您了解使用Python實(shí)現(xiàn)數(shù)據(jù)可視化時(shí)應(yīng)該了解的4種標(biāo)注,需要的朋友可以參考下2024-12-12
Pygame實(shí)戰(zhàn)練習(xí)之飛機(jī)大戰(zhàn)游戲
飛機(jī)大戰(zhàn)想必是很多人童年時(shí)期的經(jīng)典游戲,我們依舊能記得抱個(gè)老人機(jī)娛樂(lè)的場(chǎng)景,下面這篇文章主要給大家介紹了關(guān)于如何利用python寫(xiě)一個(gè)簡(jiǎn)單的飛機(jī)大戰(zhàn)小游戲的相關(guān)資料,需要的朋友可以參考下2021-09-09
Python 過(guò)濾字符串的技巧,map與itertools.imap
Python中的map函數(shù)非常有用,在字符轉(zhuǎn)換和字符遍歷兩節(jié)都出現(xiàn)過(guò),現(xiàn)在,它又出現(xiàn)了,會(huì)給我們帶來(lái)什么樣的驚喜呢?是不是要告訴我們,map是非常棒的,以后要多找它玩呢?2008-09-09

