理解 PythonDataclasses(一)

      網(wǎng)友投稿 1932 2022-05-30

      這是一個(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)。

      理解 Python 的 Dataclasses(一)

      一個(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?“”,?line?1,?in?TypeError:?‘<’?not?supported?between?instances?of?‘Number’?and?‘Number’

      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?“”,?line?1,?in? ?File?“”,?line?3,?in?__setattr__ dataclasses.FrozenInstanceError:?cannot?assign?to?field?‘val’

      因此,一個(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)容。

      上一篇:KubeCon 2019 中國(guó) - 參會(huì)主題分享
      下一篇:分享 | 撞壞遙控車后,有個(gè)技術(shù)大牛爸爸是種怎樣的體驗(yàn)
      相關(guān)文章
      亚洲AV噜噜一区二区三区| 亚洲人妖女同在线播放| 亚洲AV成人无码网天堂| 国产成人亚洲精品| 国产精品亚洲午夜一区二区三区| 久久狠狠高潮亚洲精品| 亚洲va久久久噜噜噜久久天堂| 亚洲狠狠爱综合影院婷婷| 亚洲日本中文字幕天天更新| 91在线亚洲综合在线| 亚洲H在线播放在线观看H| 亚洲av无码一区二区三区天堂古代 | 亚洲熟妇AV一区二区三区宅男| 亚洲国产成人精品久久| 亚洲欧洲日产韩国在线| 亚洲黄色免费在线观看| 亚洲成在人线中文字幕| 亚洲国产品综合人成综合网站| 亚洲同性男gay网站在线观看| 亚洲国产精品美女| 亚洲ts人妖网站| 2017亚洲男人天堂一| 亚洲精品第一国产综合野| 亚洲一区精彩视频| 亚洲AV无码资源在线观看| 亚洲乱码无人区卡1卡2卡3| 亚洲高清国产拍精品熟女| 朝桐光亚洲专区在线中文字幕| 亚洲日韩在线中文字幕综合| 亚洲av麻豆aⅴ无码电影| 亚洲国产一区二区视频网站| 亚洲伊人久久综合中文成人网| 亚洲色中文字幕无码AV| 久久久久亚洲AV成人无码网站| 亚洲综合激情视频| 亚洲精品伊人久久久久 | 亚洲色婷婷综合久久| 亚洲成色999久久网站| 久久亚洲日韩看片无码| 亚洲天堂2016| 国产精品成人亚洲|