理解 Python 的 Dataclasses(一)
這是一個(gè)包含兩部分的博文:
這一篇是 Dataclass 的特征概述
下一篇是?Dataclass fields?的概述
引言
Dataclasses?是一些適合于存儲(chǔ)數(shù)據(jù)對(duì)象(data object)的Python類。你可能會(huì)問(wèn),什么是數(shù)據(jù)對(duì)象?下面是一個(gè)并不詳盡的用于定義數(shù)據(jù)對(duì)象的特征列表:
他們存儲(chǔ)并表示特定的數(shù)據(jù)類型。例如:一個(gè)數(shù)字。對(duì)于那些熟悉對(duì)象關(guān)系映射(Object Relational Mapping,簡(jiǎn)稱 ORM)的人來(lái)說(shuō),一個(gè)模型實(shí)例就是一個(gè)數(shù)據(jù)對(duì)象。它表示了一種特定類型的實(shí)體。它存儲(chǔ)了用于定義或表示那種實(shí)體的屬性。
他們能夠被用于和同類型的其他對(duì)象進(jìn)行比較。例如,一個(gè)數(shù)字可能大于,小于或等于另一個(gè)數(shù)字。
當(dāng)然數(shù)據(jù)對(duì)象還有更多的特征,但上述內(nèi)容足以幫助你理解關(guān)鍵部分。
為了理解Dataclases,我們將實(shí)現(xiàn)一個(gè)簡(jiǎn)單的類。它能夠存儲(chǔ)一個(gè)數(shù)字,并允許我們執(zhí)行上面提到的各種運(yùn)算。
首先,我們將使用普通的類,然后我們使用?Dataclasses?來(lái)實(shí)現(xiàn)相同的結(jié)果。
但是在我們開(kāi)始之前,還是要提一下Dataclasses的用法。
Python3.7 提供了一個(gè)裝飾器dataclass,用以把一個(gè)類轉(zhuǎn)化為?dataclass。
你需要做的就是把類包裹進(jìn)裝飾器里:
from?dataclasses?import?dataclass @dataclassclass?A: ?...
現(xiàn)在,讓我們深入了解一下 dataclass 帶給我們的變化和用途。
初始化
class?Number: ????def?__init__(self,?val): ????????self.val?=?val>>>?one?=?Number(1)>>>?one.val>>>?1
用?dataclass?是這樣:
@dataclassclass?Number: ????val:int?>>>?one?=?Number(1)>>>?one.val>>>?1
以下是dataclass裝飾器帶來(lái)的變化:
無(wú)需定義?__init__,然后將值賦給?self,dataclass?負(fù)責(zé)處理它(LCTT 譯注:此處原文可能有誤,提及一個(gè)不存在的?d)
我們以更加易讀的方式預(yù)先定義了成員屬性,以及類型提示。我們現(xiàn)在立即能知道?val?是?int?類型。這無(wú)疑比一般定義類成員的方式更具可讀性。
Python 之禪: 可讀性很重要
它也可以定義默認(rèn)值:
@dataclass class?Number: ????val:int?=?0
表示
對(duì)象表示指的是對(duì)象的一個(gè)有意義的字符串表示,它在調(diào)試時(shí)非常有用。
默認(rèn)的 Python 對(duì)象表示不是很直觀:
class?Number: ????def?__init__(self,?val?=?0): ????self.val?=?val>>>?a?=?Number(1)>>>?a>>>?<__main__.Number?object?at?0x7ff395b2ccc0>
這讓我們無(wú)法知悉對(duì)象的作用,并且會(huì)導(dǎo)致糟糕的調(diào)試體驗(yàn)。
一個(gè)有意義的表示可以通過(guò)在類中定義一個(gè)__repr__方法來(lái)實(shí)現(xiàn)。
def?__repr__(self): ????return?self.val
現(xiàn)在我們得到這個(gè)對(duì)象有意義的表示:
>>>?a?=?Number(1)>>>?a>>>?1
dataclass?會(huì)自動(dòng)添加一個(gè)?__repr__函數(shù),這樣我們就不必手動(dòng)實(shí)現(xiàn)它了。
@dataclass class?Number: ????val:?int?=?0
>>>?a?=?Number(1)>>>?a>>>?Number(val?=?1)
數(shù)據(jù)比較
通常,數(shù)據(jù)對(duì)象之間需要相互比較。
兩個(gè)對(duì)象a?和?b之間的比較通常包括以下操作:
a < b
a > b
a == b
a >= b
a <= b
在 Python 中,能夠在可以執(zhí)行上述操作的類中定義方法。為了簡(jiǎn)單起見(jiàn),不讓這篇文章過(guò)于冗長(zhǎng),我將只展示?==?和?
通常這樣寫(xiě):
class?Number: ????def?__init__(?self,?val?=?0): ???????self.val?=?val????def?__eq__(self,?other): ????????return?self.val?==?other.val????def?__lt__(self,?other): ????????return?self.val?
使用?dataclass:
@dataclass(order?=?True) class?Number: ????val:?int?=?0
是的,就是這樣簡(jiǎn)單。
我們不需要定義__eq__?和__lt__?方法,因?yàn)楫?dāng)order = True?被調(diào)用時(shí),dataclass 裝飾器會(huì)自動(dòng)將它們添加到我們的類定義中。
那么,它是如何做到的呢?
當(dāng)你使用?dataclass?時(shí),它會(huì)在類定義中添加函數(shù)__eq__?和__lt__?。我們已經(jīng)知道這點(diǎn)了。那么,這些函數(shù)是怎樣知道如何檢查相等并進(jìn)行比較呢?
生成__eq__?函數(shù)的 dataclass 類會(huì)比較兩個(gè)屬性構(gòu)成的元組,一個(gè)由自己屬性構(gòu)成的,另一個(gè)由同類的其他實(shí)例的屬性構(gòu)成。在我們的例子中,自動(dòng)生成的?__eq__?函數(shù)相當(dāng)于:
def?__eq__(self,?other): ????return?(self.val,)?==?(other.val,)
讓我們來(lái)看一個(gè)更詳細(xì)的例子:
我們會(huì)編寫(xiě)一個(gè)dataclass?類?Person來(lái)保存name和?age。
@dataclass(order?=?True) class?Person: ????name:?str ????age:int?=?0
自動(dòng)生成的?__eq__?方法等同于:
def?__eq__(self,?other): ????return?(self.name,?self.age)?==?(?other.name,?other.age)
請(qǐng)注意屬性的順序。它們總是按照你在dataclass類中定義的順序生成。
同樣,等效的?__le__?函數(shù)類似于:
def?__le__(self,?other): ????return?(self.name,?self.age)?<=?(other.name,?other.age)
當(dāng)你需要對(duì)數(shù)據(jù)對(duì)象列表進(jìn)行排序時(shí),通常會(huì)出現(xiàn)像?__le__?這樣的函數(shù)的定義。Python 內(nèi)置的?sorted?函數(shù)依賴于比較兩個(gè)對(duì)象。
>>>?import?random>>>?a?=?[Number(random.randint(1,10))?for?_?in?range(10)]?#generate?list?of?random?numbers>>>?a>>>?[Number(val=2),?Number(val=7),?Number(val=6),?Number(val=5),?Number(val=10),?Number(val=9),?Number(val=1),?Number(val=10),?Number(val=1),?Number(val=7)]>>>?sorted_a?=?sorted(a)?#Sort?Numbers?in?ascending?order>>>?[Number(val=1),?Number(val=1),?Number(val=2),?Number(val=5),?Number(val=6),?Number(val=7),?Number(val=7),?Number(val=9),?Number(val=10),?Number(val=10)]>>>?reverse_sorted_a?=?sorted(a,?reverse?=?True)?#Sort?Numbers?in?descending?order? >>>?reverse_sorted_a>>>?[Number(val=10),?Number(val=10),?Number(val=9),?Number(val=7),?Number(val=7),?Number(val=6),?Number(val=5),?Number(val=2),?Number(val=1),?Number(val=1)]
dataclass 作為一個(gè)可調(diào)用的裝飾器
定義所有的?dunder(LCTT 譯注:這是指雙下劃線方法,即魔法方法)方法并不總是值得的。你的用例可能只包括存儲(chǔ)值和檢查相等性。因此,你只需定義?__init__?和?__eq__?方法。如果我們可以告訴裝飾器不生成其他方法,那么它會(huì)減少一些開(kāi)銷,并且我們將在數(shù)據(jù)對(duì)象上有正確的操作。
幸運(yùn)的是,這可以通過(guò)將?dataclass?裝飾器作為可調(diào)用對(duì)象來(lái)實(shí)現(xiàn)。
從官方文檔來(lái)看,裝飾器可以用作具有如下參數(shù)的可調(diào)用對(duì)象:
@dataclass(init=True,?repr=True,?eq=True,?order=False,?unsafe_hash=False,?frozen=False)class?C: ?…
init:默認(rèn)將生成?__init__?方法。如果傳入?False,那么該類將不會(huì)有__init__方法。
repr:__repr__?方法默認(rèn)生成。如果傳入False,那么該類將不會(huì)有__repr__方法。
eq:默認(rèn)將生成__eq__方法。如果傳入?False,那么?__eq__方法將不會(huì)被?dataclass?添加,但默認(rèn)為object.__eq__。
order:默認(rèn)將生成__gt__、__ge__、__lt__、__le__?方法。如果傳入?False,則省略它們。
我們?cè)诮酉聛?lái)會(huì)討論?frozen。由于?unsafe_hash?參數(shù)復(fù)雜的用例,它值得單獨(dú)發(fā)布一篇文章。
現(xiàn)在回到我們的用例,以下是我們需要的:
init
eq
默認(rèn)會(huì)生成這些函數(shù),因此我們需要的是不生成其他函數(shù)。那么我們?cè)撛趺醋瞿兀亢芎?jiǎn)單,只需將相關(guān)參數(shù)作為?false?傳入給生成器即可。
@dataclass(repr?=?False)?#?order,?unsafe_hash?and?frozen?are?Falseclass?Number: ????val:?int?=?0>>>?a?=?Number(1)>>>?a>>>?<__main__.Number?object?at?0x7ff395afe898>>>>?b?=?Number(2)>>>?c?=?Number(1)>>>?a?==?b>>>?False>>>?a?>>?Traceback?(most?recent?call?last): ?File?“
Frozen(不可變) 實(shí)例
Frozen 實(shí)例是在初始化對(duì)象后無(wú)法修改其屬性的對(duì)象。
無(wú)法創(chuàng)建真正不可變的 Python 對(duì)象
在 Python 中創(chuàng)建對(duì)象的不可變屬性是一項(xiàng)艱巨的任務(wù),我將不會(huì)在本篇文章中深入探討。
以下是我們期望不可變對(duì)象能夠做到的:
>>>?a?=?Number(10)?#Assuming?Number?class?isimmutable>>>?a.val?=?10?#?Raises?Error
有了?dataclass,就可以通過(guò)使用?dataclass裝飾器作為可調(diào)用對(duì)象配合參數(shù)?frozen=True來(lái)定義一個(gè)frozen對(duì)象。
當(dāng)實(shí)例化一個(gè)frozen對(duì)象時(shí),任何企圖修改對(duì)象屬性的行為都會(huì)引發(fā)?FrozenInstanceError。
@dataclass(frozen?=?True)class?Number: ????val:?int?=?0>>>?a?=?Number(1)>>>?a.val>>>?1>>>?a.val?=?2>>>?Traceback?(most?recent?call?last): ?File?“
因此,一個(gè)frozen 實(shí)例是一種很好方式來(lái)存儲(chǔ):
常數(shù)
設(shè)置
這些通常不會(huì)在應(yīng)用程序的生命周期內(nèi)發(fā)生變化,任何企圖修改它們的行為都應(yīng)該被禁止。
后期初始化處理
有了dataclass,需要定義一個(gè)?__init__方法來(lái)將變量賦給self這種初始化操作已經(jīng)得到了處理。但是我們失去了在變量被賦值之后立即需要的函數(shù)調(diào)用或處理的靈活性。
讓我們來(lái)討論一個(gè)用例,在這個(gè)用例中,我們定義一個(gè)Float?類來(lái)包含浮點(diǎn)數(shù),然后在初始化之后立即計(jì)算整數(shù)和小數(shù)部分。
通常是這樣:
import?mathclass?Float: ????def?__init__(self,?val?=?0): ????????self.val?=?val????????self.process() ????def?process(self): ????????self.decimal,?self.integer?=?math.modf(self.val)>>>?a?=?Float(?2.2)>>>?a.decimal>>>?0.2000>>>?a.integer>>>?2.0
幸運(yùn)的是,使用?post_init?方法已經(jīng)能夠處理后期初始化操作。
生成的?__init__?方法在返回之前調(diào)用?__post_init__?返回。因此,可以在函數(shù)中進(jìn)行任何處理。
import?math@dataclassclass?FloatNumber: ????val:?float?=?0.0 ????def?__post_init__(self): ????????self.decimal,?self.integer?=?math.modf(self.val)>>>?a?=?Number(2.2)>>>?a.val>>>?2.2>>>?a.integer>>>?2.0>>>?a.decimal>>>?0.2
多么方便!
繼承
Dataclasses支持繼承,就像普通的Python類一樣。
因此,父類中定義的屬性將在子類中可用。
@dataclassclass?Person: ????age:?int?=?0 ????name:?str@dataclassclass?Student(Person): ????grade:?int>>>?s?=?Student(20,?"John?Doe",?12)>>>?s.age>>>?20>>>?s.name>>>?"John?Doe">>>?s.grade>>>?12
請(qǐng)注意,Student?的參數(shù)是在類中定義的字段的順序。
繼承過(guò)程中?__post_init__?的行為是怎樣的?
由于?__post_init__只是另一個(gè)函數(shù),因此必須以傳統(tǒng)方式調(diào)用它:
@dataclassclass?A: ????a:?int????def?__post_init__(self): ????????print("A")@dataclassclass?B(A): ????b:?int????def?__post_init__(self): ????????print("B")>>>?a?=?B(1,2)>>>?B
在上面的例子中,只有?B的?__post_init__?被調(diào)用,那么我們?nèi)绾握{(diào)用A的?__post_init__?呢?
因?yàn)樗歉割惖暮瘮?shù),所以可以用?super來(lái)調(diào)用它。
@dataclassclass?B(A): ????b:?int????def?__post_init__(self): ????????super().__post_init__()?#?調(diào)用?A?的?post?init ????????print("B")>>>?a?=?B(1,2)>>>?A ????B
結(jié)論
因此,以上是?dataclass使?Python開(kāi)發(fā)人員變得更輕松的幾種方法。
存儲(chǔ) Python Elasticsearch
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。