本篇引用原文鏈接:【跟著Head First學(xué)python】8、一點(diǎn)點(diǎn)類:抽象行為和狀態(tài)
1、類的引入
?????? 我們在第七章中想要實(shí)現(xiàn)代碼的可重用、SQL連接與斷開的簡略寫法,而這需要上下文管理協(xié)議的支持,而為了使用上下文管理協(xié)議,我們引入了類。在本章中,暫時不進(jìn)行上下文管理協(xié)議與類的交互問題,而只討論類。
?????? 當(dāng)然,其他復(fù)雜功能諸如繼承與多態(tài)也暫時不考慮,我們在本章主要考慮封裝。
2、類的概述
python中的類,大抵與其他語言中的類相似:都是由方法與屬性構(gòu)成的。方法實(shí)際上就是函數(shù),屬性實(shí)際上就是變量。類就是把函數(shù)和變量打包在一起的一個結(jié)構(gòu)。
首先必須明確一點(diǎn),方法是所有屬于某個類的對象共有的,只要一個變量屬于某個類,它就可以使用該類的所有方法;但是屬性是各個變量各自私有的,不會在不同變量間共享。
專業(yè)的來講,即是:從同一個類創(chuàng)建的所有對象都可以訪問這個類的方法(共享代碼)。不過,每個對象會維護(hù)其自己的屬性副本。
我們以舉例來說明我們想要實(shí)現(xiàn)的類的某些功能。
我們要新建一個名為CountFromBy的類,它應(yīng)具有以下功能:

能夠通過這個類創(chuàng)建對象。
這個類有兩個屬性,val和incr,前者是當(dāng)前值,后者是累加值。
這個類應(yīng)具有一個方法,累加increase,每調(diào)用一次該方法,當(dāng)前值便與累加值相加一次,并將和賦給當(dāng)前值。
若是創(chuàng)建對象時未輸入?yún)?shù),應(yīng)具有默認(rèn)參數(shù)。
輸入對象名,應(yīng)返回對象的val值。

可以選擇在創(chuàng)建對象時輸入一個參數(shù),這種情況下,這個參數(shù)將賦值給val。

也可以選擇在創(chuàng)建對象時輸入兩個參數(shù),這種情況下,第一個參數(shù)將賦值給val,第二個則是incr。

還可以選擇輸入一個參數(shù),這個參數(shù)賦值給incr而不是val。
下面將按步分別實(shí)現(xiàn)以上功能。
3、功能實(shí)現(xiàn)
①、通過類創(chuàng)建對象
這是最基本的功能,python會替我們實(shí)現(xiàn)。具體來說,就類似調(diào)用函數(shù)一樣調(diào)用類名,并在后面加上小括號()即可。
那么問題來了,如何分辨某句代碼是調(diào)用了一個函數(shù)還是用類創(chuàng)建了一個對象呢?
只看某一句代碼很難分辨。于是有了一個約定俗成的規(guī)定:
a=CountFromBy()#這是類
b=count_from_by()#這是函數(shù)
即若按駝峰形式命名,說明這將創(chuàng)建一個對象,若單詞間用下劃線隔開,說明這將調(diào)用一個函數(shù)。
②、類的初始化
為什么我們調(diào)用類名就可以實(shí)現(xiàn)創(chuàng)造一個對象呢?這期間發(fā)生了什么?為什么調(diào)用類名傳入的參數(shù)可以給類賦值呢?
這是由于我們重寫了__init__這一方法。
在之前我們看見過類似的,__main__,因此并不算陌生。__init__方法,以及之后提到的一些方法,都是屬于object類的一個方法。這個object類,只看其名字,都可以知道它很有排面,敢叫這樣一個名字,它的勢力一定很大。
事實(shí)上,我們在python中建的所有類,都繼承自這個類,這個類中有一系列默認(rèn)的常用方法。我們建了一個自己的類之后,如果沒有自己寫一個方法,那就默認(rèn)使用object類中的方法。
比如說,對于一個什么都沒有的空類,AnEmptyCalss,調(diào)用這個類的時候使用如下代碼:
a=AnEmptyClass()
由于這是一個空類,因此他自己不知道自己被調(diào)用后該做什么,因此將調(diào)用object類的__init__方法,建立一個該類的對象。但是這只是權(quán)宜之計(jì),畢竟每個類都不一樣,不可能用一個方法滿足所有類的要求。因此,我們需要在自己寫的類里面重寫__init__方法。如下:
classCountFromBy:
def__init__(self,v:int=0,i:int=1)->None:
? ? ? ? self.val=v
? ? ? ? self.incr=i
在解釋這個方法之前,來看python中創(chuàng)建一個類要做的事:class+空格+類名:,很簡單。
接下來解釋一下這個方法吧。首先方法名也是__init__,這樣在創(chuàng)建對象時候?qū)⒅苯诱{(diào)用自己的__init__方法,而不會去找object的公共方法了。這個方法有三個參數(shù),self,v,i。為什么有三個呢?這個self是干嘛用的?
emmmm,有點(diǎn)難解釋,我準(zhǔn)備從函數(shù)作用域這一點(diǎn)來解釋為什么要有這個self。
眾所周知,變量在作用域內(nèi)才有效。舉一個例子,說打南邊來了個函數(shù)叫add,手里提著一個參數(shù)a,然后它在北邊變出一個變量b,add實(shí)現(xiàn)了a+b的功能,并把這個值賦給a。然后呢?然后北邊這個b又被add給拍死了。
b的生命就存在于add把它創(chuàng)建出來到add拍死他這一段時間內(nèi)。這段時間就是作用域。用完就扔,很好的節(jié)約了內(nèi)存空間。
也就是說,在我調(diào)用函數(shù)add時,在外面來看,只能看到add的參數(shù)a,b是看不到的。一旦add調(diào)用完成,a被重新賦值,b就沒了?,F(xiàn)在問題來了,我想保存b怎么辦,推廣一下,我想保存函數(shù)中所有的變量該怎么辦。
你可能這樣想,我用一個數(shù)組或者一個字典保存,當(dāng)然可以,但是有點(diǎn)蠢。如果要保存的是一個類的屬性,那么事情就變得重要,而且這是很經(jīng)常發(fā)生的。為了保存類的屬性,我們當(dāng)然要指明類了,即使這個函數(shù)是類的方法,那保存類的屬性也還是要指明,因?yàn)榭隙〞胁恍枰4娴呐R時變量,要把需要保存的類的屬性和不需保存的臨時變量分開,指明很有必要。
self就是為了指明自己而出現(xiàn)的。self的“值”,就是最開始建立的對象名。
可以這么說,如果一個函數(shù)的參數(shù)有self,那么它就是一個類的方法,這是一個小技巧,分辨方法和函數(shù)。其實(shí)這里的self和java里的this差不多。這可以推理出以下一個論斷:每個類的方法都至少有一個參數(shù)。
要注意一點(diǎn),雖然self是方法里面的第一個參數(shù),但是卻不需要在調(diào)用的時候給它指定一個值,解釋器會幫我們做這件事。舉個例子,在創(chuàng)建上面這個對象時,我們最多需要輸入兩個值,卻不能輸入三個值。
然后來看第二個和第三個參數(shù),v和i,它們是有默認(rèn)值的,這種寫法我們在之前已經(jīng)看過,這說明如果沒有給v或i賦值,那么它們的值會默認(rèn)為0和1。這很方便。
最后看函數(shù)體,這里可以很直觀的看出self的功能,v和i都可以看做是臨時變量,想把它們保存下來,只需給類的屬性val和incr賦值為v和i即可。
以上便實(shí)現(xiàn)了類的初始化。
③、類的方法
在上面我們重寫了類的__init__方法,如果我們想寫一個自己的方法,也十分類似。注意我們的方法的功能,val+incr,也就是說,不需要額外的參數(shù)。因此我們的方法increase的參數(shù)只有一個,那就是self。代碼如下:
defincrease(self)->None:
? ? self.val+=self.incr
可以說是很簡單了。
在調(diào)用的時候,我們需要按如下形式:
a.increase()
在運(yùn)行時,解釋器會把它改成如下形式:
CountFromBy.increase(c)
這也就是我們在上面所說的“解釋器會幫我們做這件事”,這樣increase的參數(shù)就被指定了,一切都圓了回來。我們當(dāng)然也可以使用這種方式調(diào)用類的方法,但這純屬多此一舉。
④、對象的表達(dá)
當(dāng)我們聲明了某個類的一個對象,然后print這個類,會發(fā)生什么?一起來看看。

我們可能想要的是a的val值,但是計(jì)算機(jī)不知道,它覺得val和incr一樣好看,憑什么print一下a就要輸出a的val呢?它不輸出這個。它輸出一串神秘代碼。我們來看這段代碼的含義。
嗯,前面這個“__main__.CountFromBy”看起來是這個變量a的類型,是CountFromBy。后面這個是什么?
我們將使用兩個常用函數(shù):type和id。
type函數(shù)用于顯示創(chuàng)建這個對象的類的有關(guān)信息。
id函數(shù)用于顯示對象內(nèi)存地址的有關(guān)信息。如下:

嗯,這個type和上面對應(yīng)上了,但是id沒有對應(yīng)上啊。別急,把id變?yōu)?6進(jìn)制,利用函數(shù)hex,如下:

完美對應(yīng)a的后半段,只是大寫變成了小寫。
這說明在默認(rèn)時,打印某個對象將會輸出該對象的類的信息,并輸出它所在的內(nèi)存地址。很多時候這不是我們需要的信息,怎么辦呢?重寫。
是的,上面的功能是object中的一個方法實(shí)現(xiàn)的,這個方法叫__repr__。如果想要實(shí)現(xiàn)打印某個對象的個性化,我們要重寫__repr__。如下:
def__repr__(self)->str:
returnstr(self.val)
很簡單,我們這里選擇返回對象的val值的str格式。這樣我們在打印對象的時候,將會返回想要的值了。
⑤、對象的個性化初始化3
實(shí)際上,這一功能在我們實(shí)現(xiàn)重寫__init__的時候就已經(jīng)實(shí)現(xiàn)了。唯一要注意的一點(diǎn)就是若想單獨(dú)為incr賦值,應(yīng)與def中的相同,在初始化時指明參數(shù)為i,而不能是incr,因?yàn)槌跏蓟瘜?shí)際上就是調(diào)用__init__函數(shù),這個函數(shù)只認(rèn)識i,而不認(rèn)識incr。
以上步驟完成后,接下來就可以學(xué)習(xí)上下文協(xié)議的有關(guān)內(nèi)容了