【Python数据结构】 抽象数据类型 Python类机制和异常
這篇是《數(shù)據(jù)結(jié)構(gòu)與算法Python語(yǔ)言描述》的筆記,但是大頭在Python類機(jī)制和面向?qū)ο缶幊痰恼f(shuō)明上面。我也不知道該放什么分類了。。總之之前也沒怎么認(rèn)真接觸過(guò)基于類而不是獨(dú)立函數(shù)的Python編程,借著本次機(jī)會(huì)仔細(xì)學(xué)習(xí)一下。
抽象數(shù)據(jù)類型
最開始的計(jì)算機(jī)語(yǔ)言,關(guān)注的都是如何更加有效率地計(jì)算,可以說(shuō)其目的是計(jì)算層面的抽象。然而隨著這個(gè)行業(yè)的不斷發(fā)展,計(jì)算機(jī)不僅僅用于計(jì)算,開發(fā)也不僅只關(guān)注計(jì)算過(guò)程了,數(shù)據(jù)層面的抽象也變得同樣重要。雖然計(jì)算機(jī)語(yǔ)言一開始就有對(duì)數(shù)據(jù)的抽象,但是那些都只是對(duì)一些最基本的數(shù)據(jù)類型而不包括我們想要用的,多種多樣的數(shù)據(jù)。
數(shù)據(jù)類型:
程序處理的數(shù)據(jù),通常是不同的類型的。只有事先約定好的不同類型的數(shù)據(jù)的存儲(chǔ)方式,計(jì)算機(jī)才能正確理解邏輯上不同的數(shù)據(jù)類型。所有編程語(yǔ)言都會(huì)有一組內(nèi)置的基本數(shù)據(jù)類型。另外在實(shí)際工作過(guò)程中,或早或晚總會(huì)碰到一些沒法用現(xiàn)有數(shù)據(jù)類型解決的問(wèn)題,這時(shí)就需要自定義一些數(shù)據(jù)類型來(lái)解決。像Python這樣比較高級(jí)的語(yǔ)言的話,在基本類型的基礎(chǔ)上還添加了一些額外的數(shù)據(jù)結(jié)構(gòu)如tuple,list,dict(這些廣義上來(lái)說(shuō)也算是Python的數(shù)據(jù)類型)。
■ 抽象數(shù)據(jù)類型
以上基本數(shù)據(jù)類型都是比較simple,naive的結(jié)構(gòu),而且有一點(diǎn)很違和的是,以上數(shù)據(jù)結(jié)構(gòu)都把數(shù)據(jù)暴露在外。如果一個(gè)人有了對(duì)某個(gè)變量的權(quán)限的話他就可以看到這個(gè)變量代表的數(shù)據(jù)結(jié)構(gòu)中的所有數(shù)據(jù)。為了解決這個(gè)問(wèn)題,必須要有一種數(shù)據(jù)類型,它可以讓使用者只需要考慮如何使用這種類型的對(duì)象,而不需要(或者根本不能)去關(guān)注對(duì)象內(nèi)部的實(shí)現(xiàn)方式以及數(shù)據(jù)的表示等等。這樣的對(duì)象和類型從概念上來(lái)說(shuō)就是抽象數(shù)據(jù)對(duì)象和抽象數(shù)據(jù)類型了。
抽象數(shù)據(jù)類型的基本想法是把數(shù)據(jù)定義為抽象的數(shù)據(jù)對(duì)象集合,只為他們定義可用的合法操作而不暴露內(nèi)部實(shí)現(xiàn)的具體細(xì)節(jié),不論是操作細(xì)節(jié)還是數(shù)據(jù)的存儲(chǔ)細(xì)節(jié)。在這樣的思想指導(dǎo)下,一般而言的抽象數(shù)據(jù)類型應(yīng)該具有下列三種操作:
1. 構(gòu)造操作(比如python類中的__init__方法)
2. 解析操作(getxx方法)
3. 變動(dòng)操作(setxx方法)
看到這三種操作之后,根據(jù)這三個(gè)性質(zhì)可以區(qū)分出數(shù)據(jù)類型的變動(dòng)性。如果一個(gè)類型只有1和2兩種操作那么就是不可變的類型,如果一個(gè)類型具備三種操作,那么就是一個(gè)可變的類型。在Python中,對(duì)象分成可變和不可變的,從抽象數(shù)據(jù)類型的角度來(lái)看就是看這個(gè)類型有沒有變動(dòng)操作的一個(gè)判斷。
?
■ Python的類
Python中的類就是一種抽象數(shù)據(jù)類型的實(shí)現(xiàn),定義好的一個(gè)類就像是一個(gè)系統(tǒng)內(nèi)部類型,可以產(chǎn)生該類型的對(duì)象(或者也可以叫它實(shí)例),實(shí)例具有這個(gè)類所描述的行為。實(shí)際上,Python的內(nèi)置類型也都可以看做是類的一種從而進(jìn)行一些類似“類”的操作。
關(guān)于類如何定義,一些基本的方法看下基本教程就懂了。之前有接觸過(guò)一點(diǎn)java,總體來(lái)說(shuō),python的類的定義方法和java是類似的,而且比java要簡(jiǎn)單一點(diǎn)(比如python中定義類和類中的方法時(shí)不必指出類的公用性有多大,比如是public,private還是其他什么標(biāo)志)
下面講些稍微高端一點(diǎn)的類定義的規(guī)范和方法
●? 關(guān)于內(nèi)部使用的屬性和方法
在java里面,在類內(nèi)部使用的方法和屬性通常要加上修飾符private使得外部的調(diào)用者沒辦法直接訪問(wèn)這些方法和屬性。Python中也有類似的機(jī)制,分成兩種形式。一是把這種只提供給內(nèi)部使用的屬性(方法)的名字前面加上一個(gè)下劃線以提示其私有的性質(zhì),這樣的寫法并不是語(yǔ)言規(guī)定而是人們約定俗成的,也就是說(shuō)如果你想要通過(guò)實(shí)例直接訪問(wèn)一個(gè)下劃線開頭的屬性或者屬性方法也是可行的只不過(guò)不鼓勵(lì)這么做。第二種形式是以兩個(gè)下劃線開頭作為屬性(方法)的名字,當(dāng)然它不能同時(shí)以兩個(gè)下劃線結(jié)尾,這樣就變成了魔法方法了。這種兩個(gè)下劃線的形式的屬性是和java中的private一樣的,如果從類的外部去訪問(wèn)這個(gè)屬性的話會(huì)拋出AttributeError提示找不到相關(guān)屬性。
●? 關(guān)于類屬獨(dú)立屬性(類變量)
有時(shí)候,可以在類中的所有屬性方法外面添加一些屬性。這些嚴(yán)格來(lái)說(shuō)都已經(jīng)不算是類的屬性了,因?yàn)樗麄?strong>和類的實(shí)例是完全不搭界的,有點(diǎn)像Java中的static類變量。對(duì)于這類“屬性”幾點(diǎn)想說(shuō):
1. 因?yàn)檫@些屬性常常寫在類的最上面,有時(shí)候可能會(huì)受到j(luò)ava的影響而下意識(shí)的以為這些屬性是跟實(shí)例關(guān)聯(lián)的,實(shí)則不然。就像函數(shù)參數(shù)的默認(rèn)值一樣,在函數(shù)被定義好的時(shí)候就被初始化好并保存在內(nèi)存中的特定地址里不會(huì)隨著調(diào)用函數(shù)次數(shù)的變化而變化一樣,類屬獨(dú)立屬性是在類被定義好的時(shí)候就被保存了起來(lái),不會(huì)因?yàn)轭惐粚?shí)例化了多少次而被初始化多少次,而且不論通過(guò)類名還是實(shí)例去調(diào)用它它的值都是一樣的(也就是說(shuō)python中的“類變量”是自帶static屬性的)。所以在比如類需要統(tǒng)計(jì)一共被實(shí)例化了多少次的場(chǎng)景中,可以在類中的所有方法外寫一個(gè)count = 0然后在類的__init__方法中添加一句count+=1。這樣每次實(shí)例化調(diào)用__init__的時(shí)候會(huì)讓count加上1而不是初始化回0的狀態(tài)。
2. 這種屬性也不能理解成類的局部變量。在這個(gè)類的方法中我們不能直呼其名地調(diào)用這些屬性,而是得像在類外面一樣通過(guò)圓點(diǎn)的形式來(lái)調(diào)用相關(guān)屬性。比如:
class Counter():count = 0def __init__(self):count += 1 #這會(huì)報(bào)錯(cuò)Counter.count += 1 #必須這么寫當(dāng)然如果是類方法的話可以在方法中用cls.attribute的形式調(diào)用相關(guān)屬性。下面也會(huì)有提到。
注意:在類的屬性方法中是可以通過(guò)self.attribute的形式來(lái)調(diào)用類屬獨(dú)立屬性的,在類定義外面也可以通過(guò)o.attribute來(lái)調(diào)用類屬獨(dú)立屬性,但是要明確一點(diǎn),類在實(shí)例化時(shí),將類獨(dú)立屬性的名字告訴實(shí)例并把地址賦給它。但是之后實(shí)例所得到的類獨(dú)立屬性已經(jīng)和原來(lái)的類獨(dú)立屬性有了區(qū)別,如果屬性值是可變對(duì)象,那么實(shí)例對(duì)它自身的類獨(dú)立屬性修改會(huì)反映到類調(diào)用類獨(dú)立屬性時(shí)的值,如果是不可變對(duì)象,那么實(shí)例對(duì)它自身的類獨(dú)立屬性修改是不影響類調(diào)用類獨(dú)立屬性時(shí)的值的,比如(寫得有些凌亂。。可以參考下面寫的【對(duì)python類實(shí)例化時(shí)操作的一些思考】):
class Test(object):num = 1lst = [1]lst2 = [1]t = Test() t.num = 2 t.lst.append(2) t.lst2 = [2] print Test.num,Test.lst,Test.lst2#得到結(jié)果是1 [1, 2] [1]另外成功調(diào)用還要建立在一個(gè)基礎(chǔ)上,即這么調(diào)用的那個(gè)實(shí)例本身沒有和類屬獨(dú)立屬性重名的屬性存在。比如:
class Test(object):count = 1def __init__(self):self.count = 2def get_count(self):print self.count t = Test() t.get_count() del(t.count) #可以通過(guò)del函數(shù)解除一個(gè)屬性和實(shí)例的關(guān)系,即刪除屬性的操作。 t.get_count() #結(jié)果 #2 #1 #第一次調(diào)用get方法的時(shí)候,t先找自身喲沒有名為count的屬性,找到就返回了。但是第二回的時(shí)候,在自身沒有找到而在類中找到了一個(gè)獨(dú)立屬性名為count,而它也是可以調(diào)用的,所以就返回了那個(gè)count如果無(wú)論如何都有同名的變量的話,那么可以通過(guò)t.__class__.count來(lái)指定訪問(wèn)屬于類的類變量。另外就上面給出的那個(gè)Counter.count+=1的例子而言,這里如果換成了self.count += 1的話可能達(dá)不到目的。原因在之前的某篇文章中也提到過(guò),即python中的賦值語(yǔ)句還兼顧了聲明變量的功能。這里的self.count = self.count + 1,右邊的self.count確實(shí)引用了類屬獨(dú)立屬性的count,然而左邊的self.count被解釋成為實(shí)例自身添加一個(gè)count的屬性。因此如果寫成self.count +=1的話,作為類屬獨(dú)立屬性的count始終是0而每個(gè)被初始化出來(lái)的Counter的實(shí)例里面會(huì)有一個(gè)count的屬性覆蓋了類屬獨(dú)立屬性,其值是1。
3. self.attribute是不能寫在外面的!總是把self誤認(rèn)為是類對(duì)象,其實(shí)應(yīng)該是調(diào)用時(shí)的實(shí)例對(duì)象。換句話說(shuō),在所有方法外面寫self.attribute的話,self是什么東西解釋器是不知道的。唯有在寫方法中(which的第一個(gè)參數(shù)是self),在調(diào)用方法的時(shí)候解釋器可以把調(diào)用方法的實(shí)例作為參數(shù)約束給self,這樣self才會(huì)有意義。
●? 關(guān)于靜態(tài)方法
在java中,可以用提示符static來(lái)表明某個(gè)方法是獨(dú)立于其他同一個(gè)類中其他方法的靜態(tài)方法。所謂靜態(tài)方法就是說(shuō)要調(diào)用這個(gè)方法可以不必通過(guò)類的實(shí)例而直接通過(guò)類名來(lái)調(diào)用。在Python中,類本身也是一個(gè)對(duì)象,通過(guò)類名本身來(lái)調(diào)用一個(gè)方法看起來(lái)似乎合情合理,為了滿足這種需求,Python的類中可以通過(guò)添加修飾符@staticmethod來(lái)使得一個(gè)方法變成類的靜態(tài)方法。靜態(tài)方法的參數(shù)中沒有self并且也可以通過(guò)類的實(shí)例來(lái)調(diào)用。從某種意義上說(shuō),靜態(tài)方法其實(shí)算是類里面定義的普通函數(shù),是一個(gè)類的局部函數(shù)。
●? 關(guān)于類方法
和靜態(tài)方法類似的,類方法用@classmethod修飾符來(lái)表示。類方法和普通的屬性方法一樣,一般自帶一個(gè)參數(shù)叫cls,在方法中代表調(diào)用這個(gè)類方法的類對(duì)象(通常是正在定義的這個(gè)類或者其父類或子類),然后在方法體中就可以用cls
來(lái)refer to這個(gè)類本身啦。比如書上有這樣一個(gè)例子
class Counter(object):count = 0def __init__(self):Counter.count += 1@classmethoddef get_count(cls):return cls.countx = Counter() print x.get_count() y = Counter() print y.get_count()######結(jié)果是 1 2?
這個(gè)例子說(shuō)明了兩個(gè)問(wèn)題。一,對(duì)于類方法而言,其參數(shù)cls在調(diào)用時(shí)確實(shí)約束到了調(diào)用它的那個(gè)類對(duì)象上。二,對(duì)于屬于類本身的獨(dú)立屬性,其并不根據(jù)實(shí)例的初始化而初始化。
●? 類中的魔法方法
關(guān)于魔法方法的說(shuō)明可以參考魔法方法那篇筆記,這里不多提。想說(shuō)的是一個(gè)小技巧,比如在一個(gè)類中要定義一個(gè)比大小的魔法方法,而比較的類的一個(gè)屬性的時(shí)候,下意識(shí)的總會(huì)寫
def __lt__(self,another):if self._attribute < another._attribute:return Trueelse:return False但是實(shí)際上可以這么寫更簡(jiǎn)潔,而且因?yàn)槟脕?lái)作比較外部實(shí)例的不一定也有_attribute這個(gè)屬性,最好還能加上一個(gè)異常排除的過(guò)程:
def __lt__(self,another):try:return self._attribute < another._attributeexcept AttributeError as e:raise e? ●? 關(guān)于__init__和構(gòu)造方法
如果一個(gè)類中定義了__init__方法,那么在創(chuàng)建這個(gè)類的實(shí)例時(shí)解釋器后自動(dòng)調(diào)用這個(gè)方法初始化這個(gè)對(duì)象。之前一直認(rèn)為python類中的__init__方法就是java中的構(gòu)造方法。其實(shí)這兩者還是有些微妙的區(qū)別的。比如java的一個(gè)類中不能沒有構(gòu)造方法(好像是這樣吧= =),但是python的一個(gè)類中可以沒有__init__方法,沒有__init__方法時(shí)所有基于這個(gè)類創(chuàng)建實(shí)例的動(dòng)作都會(huì)創(chuàng)建出一個(gè)空實(shí)例,此時(shí)解釋器實(shí)際上調(diào)用的是object()方法,而object是Python中所有類的父類。
*關(guān)于python類實(shí)例化時(shí)操作的一點(diǎn)探索:python類在實(shí)例化的時(shí)候,會(huì)把類的所有成員對(duì)象(包括類獨(dú)立屬性和類方法)復(fù)制一個(gè)副本給實(shí)例,而這些成員對(duì)象的引用都指向類定義中成員對(duì)象的值。由于是副本,所以我們可以對(duì)實(shí)例的成員對(duì)象做出引用遷移,即用等號(hào)賦值以改變其引用,這樣的話實(shí)例做出的屬性改變就和類無(wú)關(guān)。如果我們通過(guò)實(shí)例改變一些可變的成員對(duì)象的值,那么會(huì)引起類中成員對(duì)象的變化。這就導(dǎo)致了下次實(shí)例化時(shí),本次的變化會(huì)體現(xiàn)在下個(gè)實(shí)例中。請(qǐng)看下面的例子:
class Test(object):num = 1lst = [1]def __init__(self):passdef method_in_class(self):print "in_class_method is called"def method_out_class():print "out_class_method is called"t = Test() t.num = 2 t.lst.append(2) t.method_in_class = method_out_class t.method_in_class() #打印結(jié)果 out_class_method is called k = Test() print k.num,k.lst #打印結(jié)果 1 [1,2],可以看到因?yàn)閘st是可變對(duì)象,t對(duì)lst做出的改變反映在了k里 k.method_in_class() #打印結(jié)果 in_class_method is called。函數(shù)也是對(duì)象,不過(guò)t做出的改變是賦值,相當(dāng)于改變了t.method_in_class方法的引用,不影響類定義中的method_in_class。所以k是不受影響?? ●? 關(guān)于一般成員方法的屬性化
在java中,常常會(huì)在類中聲明一個(gè)private變量var,然后再類中設(shè)計(jì)getVar()和setVar(value)方法來(lái)通過(guò)方法的包裝實(shí)現(xiàn)對(duì)var的改變。當(dāng)在python中進(jìn)行面向?qū)ο缶幊虝r(shí),也偶爾會(huì)用到這種模式,比如:
class Test():def __init__(self):self.var = Nonedef getVar(self):return self.vardef setVar(self,value):self.var = value?
但是這樣做有一個(gè)問(wèn)題,在類的實(shí)例被初始化之后,我們可以直接通過(guò)實(shí)例名.var的方式對(duì)var做出改變,這使得setVar和getVar兩個(gè)方法顯得沒有意義而且不安全。java中有private這種關(guān)鍵字可以解決這個(gè)問(wèn)題,有人會(huì)說(shuō)python中可以把var改成_var可以提示這是個(gè)私有變量。不過(guò)_var只是一個(gè)提示性而不是強(qiáng)制性的,更重要的是用setVar和getVar方法來(lái)做這件事略顯麻煩,不符合python一切從簡(jiǎn)的原則。基于這樣一種思想,python提供了@property這種裝飾器,其作用是自動(dòng)把類似于setVar,getVar這種樣子的變量變更機(jī)制轉(zhuǎn)換成更加簡(jiǎn)潔的調(diào)用形式同時(shí)不繞開setVar和getVar的代碼。一個(gè)典型的例子:
class Test(object):def __init__(self):self._var = None@propertydef var(self):raise Exception("var is not a readable attribute")@var.setterdef var(self,value):if isinstance(value,str):self._var = valuet,k = Test(),Test() t.var,k.var = 123,"abc" print t.var,k.var #結(jié)果是None abc?
從根本上看,@property裝飾的函數(shù)可以被看成是一個(gè)類的屬性,通過(guò) “實(shí)例.函數(shù)名”的方式就可以調(diào)用其返回的值。另一方面,引申出了@函數(shù)名.setter這個(gè)裝飾器,其裝飾的函數(shù)可以看做是setVar方法,只不過(guò)可以直接通過(guò)賦值語(yǔ)句調(diào)用。在這個(gè)例子中可以看到,@var.setter裝飾的函數(shù)檢查要設(shè)置的值是否為字符串類型,只有字符串類型才設(shè)置上去,所以t.var = 123并不成功,但k.var = "abc"成功了。如此可以讓調(diào)用屬性變得簡(jiǎn)潔,同時(shí)實(shí)際上我們是隱形地調(diào)用了類似于setVar,getVar的方法,可以在方法中加上一些控制條件以完善安全性或其他性能。
另外需要注意的是@property下的函數(shù)名、@xxx.setter中的xxx以及被它修飾的函數(shù)的函數(shù)名最好都能一致,以體現(xiàn)他們都是為同一個(gè)類的成員屬性服務(wù)的。
■ 類的使用和對(duì)象(實(shí)例)
某個(gè)程序基于類C創(chuàng)建了實(shí)例o然后用o以調(diào)用屬性方法的形式調(diào)用了方法m,前半個(gè)過(guò)程中python解釋器的工作原理可能是跟我上面說(shuō)的【關(guān)于python類實(shí)例化時(shí)操作的一點(diǎn)探索】有關(guān),而這后半個(gè)過(guò)程中,python解釋器是這樣工作的。創(chuàng)建一個(gè)空方法對(duì)象,約束對(duì)象o和方法m到這個(gè)方法對(duì)象上去。正如大家所知,類中的屬性方法在定義的時(shí)候通常第一個(gè)參數(shù)是self,這是因?yàn)樾枰岩粋€(gè)實(shí)例作為一個(gè)參數(shù)傳遞到方法中去這就是為什么方法對(duì)象約束的不僅僅是m還有o的原因。當(dāng)o被作為第一個(gè)參數(shù)傳遞給m之后,m里面的self指的就是o這個(gè)實(shí)例了。
對(duì)于靜態(tài)方法,在定義的時(shí)候就沒有要寫self,所以自然就沒必要約束調(diào)用它的實(shí)例,這也是為什么它可以用類名直接調(diào)用的原因了。
其實(shí)從上面的說(shuō)明中已經(jīng)不難看出,一般屬性方法調(diào)用時(shí)的o.m()其實(shí)等價(jià)于C.m(o)
●? 關(guān)于增刪屬性和屬性的賦值語(yǔ)句
python的類的實(shí)例屬性都維護(hù)在實(shí)例的__dict__這個(gè)隱藏屬性中。因?yàn)樗旧砭褪且粋€(gè)字典,所有我們可以動(dòng)態(tài)地對(duì)某個(gè)實(shí)例的屬性做出增刪操作。操作具體不用通過(guò)__dict__這個(gè)變量,而是直接通過(guò)o.new_attribute = "new_value"的形式。當(dāng)o已經(jīng)存在new_attribute這個(gè)名稱的屬性的時(shí)候,這個(gè)屬性的內(nèi)容會(huì)被新的賦值語(yǔ)句覆蓋掉。而刪除操作可以通過(guò)del(o.attribute)來(lái)實(shí)現(xiàn)。
至于在類定義的內(nèi)部,可以在任何一個(gè)方法中通過(guò)self.new_attribute = xxx來(lái)實(shí)現(xiàn)為類添加一個(gè)新屬性。這就出現(xiàn)了一個(gè)很有意思的現(xiàn)象,所有類似于self.new_attribute=xxx都會(huì)被看做是為類添加新屬性或者改變現(xiàn)有屬性值的行為。
●? 在一個(gè)屬性方法中調(diào)用另一個(gè)屬性方法
同一個(gè)類中如果出現(xiàn)這種情況,就可以通過(guò)self.another_method()的形式來(lái)調(diào)用實(shí)現(xiàn),而不是直接another_method()。另外,如果調(diào)用這個(gè)語(yǔ)句的不是本類的實(shí)例而是一個(gè)子類的實(shí)例,然后這個(gè)子類還沒有重寫這個(gè)語(yǔ)句所在的方法但是卻重寫了another_method這個(gè)方法的話,這就導(dǎo)致了一個(gè)問(wèn)題(我靠我都暈了,實(shí)例看下):我應(yīng)該執(zhí)行哪個(gè)類中的another_method,是父類還是子類的。
class Parent():def f(self):self.g()def g(self):print "this is method g in Parent"class Child(Parent):def g(self):print "this is method g in Child"c = Child() c.f()##結(jié)果是 #this is method g in Child這種現(xiàn)象說(shuō)好聽一點(diǎn)叫動(dòng)態(tài)約束,因?yàn)閏在調(diào)用方法f的時(shí)候傳遞給f的參數(shù)self的是c實(shí)例本身,而通過(guò)self調(diào)用的g自然是通過(guò)實(shí)例c調(diào)用的g,也就是類Child中定義的g方法了。
■ 類的繼承
上面這個(gè)例子其實(shí)已經(jīng)提到了類的繼承了。python中類的繼承機(jī)制和java也都差不多,不同的是python中支持多類繼承一類也支持一類繼承多類,后者在java中好像是不行的吧(記不太清了。。)。還是講一下在實(shí)際運(yùn)用過(guò)程中可能會(huì)碰到的一些問(wèn)題
●? issubclass和isinstance
isinstance函數(shù)用來(lái)檢查某個(gè)對(duì)象是不是某個(gè)類的實(shí)例,issubclass用來(lái)檢查一個(gè)類是不是另一個(gè)類的子類。對(duì)于多層繼承,比如class A(),class B(A), class C(B)的情況,issubclass(C,A)返回True。同時(shí)子類的實(shí)例也被默認(rèn)為也是父類的實(shí)例,所以isinstance(C(),A)和isinstance(C(),B)也都是返回True的。
●? 在子類中調(diào)用父類的初始化方法 super函數(shù)
在java中,似乎是用super()指代父類的構(gòu)造方法的。python中也有super方法不過(guò)用法不太一樣:在python中如果想要在子類中調(diào)用父類的初始化構(gòu)造方法有兩種寫法,分別是
Parent.__init__(self,...)和super().__init__(...)。前一種很好理解,相當(dāng)于是通過(guò)父類對(duì)象來(lái)調(diào)用其初始化方法,把子類初始化時(shí)的那個(gè)實(shí)例作為父類初始化方法的參數(shù)來(lái)執(zhí)行。至于第二個(gè),在python中的super函數(shù)其實(shí)是返回一個(gè)父類的實(shí)例,通過(guò)實(shí)例來(lái)調(diào)用自然就不用self參數(shù)了。就調(diào)用父類的初始化方法而言,還是建議用前面一種,因?yàn)楫吘褂靡粋€(gè)創(chuàng)建后立刻銷毀的對(duì)象作為父類初始化方法的self參數(shù)總感覺有點(diǎn)不太對(duì)勁。而對(duì)于父類中的其他方法,如果想要調(diào)用那么可以super().method(...)這樣是很自然的。(這部分存疑。。書上是這么說(shuō)的但是實(shí)驗(yàn)一下通不過(guò),報(bào)錯(cuò)說(shuō)在python2中super()必須要有一個(gè)參數(shù),而書上用的是python3)當(dāng)然也可以通過(guò)Parent.method(self,...)的形式來(lái)調(diào)用。已經(jīng)證實(shí),super()只是Python3中的寫法。
super函數(shù)還有另外一種寫法,就是super(Class,object).method(...),這個(gè)語(yǔ)句可以出現(xiàn)在程序的任何地方而不一定要是在類的屬性方法定義中。這個(gè)語(yǔ)句的意思是從Class類的父類開始逐級(jí)向上搜索前輩類,在類中找到屬性方法method之后把object這個(gè)Class的實(shí)例作為method的self參數(shù)傳遞過(guò)去,然后執(zhí)行method。比如上面那個(gè)動(dòng)態(tài)約束的例子,在Child類中重寫一下f方法:
def f(self):super(Child,self).g()#這樣運(yùn)行的結(jié)果就變成了 #this is method g in Parent在這個(gè)f方法中,通過(guò)super函數(shù)強(qiáng)行把g方法關(guān)聯(lián)到父類中的g方法而不是動(dòng)態(tài)約束到子類中的g方法。
●? 關(guān)于super的具體機(jī)理
上面關(guān)于super函數(shù)的工作原理非常不細(xì)致。。“從Class類的父類開始逐級(jí)向上搜索前輩類”這句話不嚴(yán)謹(jǐn)。很明顯的一個(gè)漏洞是,Python中存在著多重繼承的情況,如果一個(gè)類繼承了多個(gè)類那么Class類的父類該選擇哪個(gè)呢?也就是說(shuō),super函數(shù)返回了一個(gè)父類實(shí)例,究竟是不是父類的實(shí)例,又是哪個(gè)父類,是怎么決定的。
首先要了解,Python中的類有一個(gè)魔法屬性叫做__mro__,MRO的全稱是Method Resolution Order即方法解析順序。其值是一個(gè)元組,元組嘛自然是有0-n的順序的。這個(gè)元組的內(nèi)容就是以本類為0號(hào)位,object這個(gè)最高的基類作為最后一位,中間是按照一定算法排列的本類的所有先輩類,這樣一個(gè)元組。比如一個(gè)class A(object)的__mro__就是(<class '__main__.A'>, <type 'object'>),而如果再來(lái)個(gè)class B(A),那么就是(<class '__main__.B'>, <class '__main__.A'>, <type 'object'>)。
知道了__mro__之后,其實(shí)super(cls,inst)函數(shù)的本意是在inst對(duì)象的__mro__元組中,尋找cls類的后一個(gè)類,返回的是這個(gè)類,然后如果后面還跟了調(diào)用方法的操作,那么此時(shí)通過(guò)這個(gè)類以及super中inst參數(shù)的值作為一個(gè)實(shí)例來(lái)調(diào)用的。對(duì)于剛提到的B,super(B,self),由于self就是自身類的實(shí)例,__mro__如上面提到的那樣,而cls參數(shù)值是B,即<class '__main__'.B>,尋找它的下一個(gè)類即A類,所以super最終返回了A類。
雖然看起來(lái)大多數(shù)情況下super就是返回了父類的實(shí)例,但是實(shí)際上super函數(shù)和父類沒啥關(guān)系,它尋找下一個(gè)類的依據(jù)是__mro__屬性這個(gè)元組,并且返回的不是這個(gè)類的實(shí)例而是這個(gè)類本身!
比如下面這段代碼很好地說(shuō)明了這點(diǎn):
class A(object):def go(self):print 'AAA GO'class B(A):def go(self):super(B,self).go()print 'BBB GO'class C(A):def go(self):super(C,self).go()print 'CCC GO'class D(B,C):def go(self):super(D,self).go()print 'DDD GO'print D.__mro__ d = D() d.go()''' 結(jié)果是 (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>) AAA GO CCC GO BBB GO DDD GO '''?
首先看到了D的__mro__順序是D、B、C、A,這個(gè)順序是Python內(nèi)部通過(guò)一種叫做C3算法的廣度優(yōu)先算法計(jì)算出來(lái)的。
實(shí)例化D后調(diào)用D的go方法,進(jìn)入方法之后,第一次調(diào)用了super,此時(shí)inst參數(shù)是self即D類實(shí)例,cls參數(shù)是D類,所以super返回的B類。變成了B類調(diào)用go方法,然后go中要求的self參數(shù)用的就是第一次調(diào)用super時(shí)的那個(gè)inst參數(shù)即一個(gè)d類實(shí)例,好,進(jìn)入B類的go方法。現(xiàn)在第二次調(diào)用super,此時(shí)inst參數(shù)的值是self,但是注意!self還是剛才的d類實(shí)例!于是神奇的一幕發(fā)生了,__mro__還是剛才那個(gè)mro,但是第一個(gè)參數(shù)由D變成了B,B的下一個(gè)類是C而不是A。因此,繼續(xù)調(diào)用go方法時(shí),調(diào)用的主體不是A類而是C類!進(jìn)入C類的go方法之后同理,mro還是同一個(gè),找到下一個(gè)是A類。A類沒啥說(shuō)的,直接print,完了返回之后回到C類go方法。所以stdout中第二行輸出是C,第三行輸出是B,而不是我們所想當(dāng)然的反過(guò)來(lái)的情況。
?
python異常
Python中的所有異常都是作為一種類而存在的,Python系統(tǒng)給出了很多系統(tǒng)自帶的常用的異常。一般如果用戶需要自定義異常的話可以選擇某一個(gè)異常作為父類來(lái)衍生出一個(gè)自定義異常。所有異常的總父類是Exception類。
所有的異常類在初始化的時(shí)候都可以接受一個(gè)字符串作為錯(cuò)誤信息,比如在自定義一個(gè)異常類的時(shí)候可以:
class MyException(Exception):def __init__(self):Exception.__init__(self,"My Exception is Raised")raise MyException#結(jié)果 # Traceback (most recent call last): # File "D:/PycharmProjects/TestProject/test.py", line 8, in <module> # raise MyException # __main__.MyException: My Excpetion is Raised如果對(duì)錯(cuò)誤判斷沒有太多要求的話可以方便地raise Exception("some error message")來(lái)拋出一個(gè)有提示文字的錯(cuò)誤。但是在更多情況下,我們可能會(huì)根據(jù)具體的業(yè)務(wù)要求來(lái)對(duì)代碼運(yùn)行做一些邏輯判斷,比如某些情況下可以拋出我們的自定義異常,然后在調(diào)用這些代碼的時(shí)候except我們的自定義異常,這樣就可以做到符合業(yè)務(wù)邏輯的異常捕獲和處理了。
●? 異常的傳播和捕獲
如果異常發(fā)生在一個(gè)try語(yǔ)句塊里面,那么解釋器將按照順序先檢查這個(gè)try語(yǔ)句塊相應(yīng)的except語(yǔ)句塊有沒有為這個(gè)異常準(zhǔn)備好處理器,如果沒有的話那么就把這個(gè)異常交給更外層的try語(yǔ)句(如果有的話),這樣逐層傳播異常,如一直傳播到這個(gè)異常所在的函數(shù)的最外層也沒能找到相關(guān)的處理器的話那么這個(gè)函數(shù)就將異常中止運(yùn)行,程序也因此整個(gè)中止。
如果在搜索過(guò)程中找到了相關(guān)的異常處理器的話,那么把執(zhí)行點(diǎn)從異常語(yǔ)句的地方跳到處理器頭部(也就是說(shuō)跳過(guò)了try語(yǔ)句塊中從異常位置到最后部分的所有代碼)。在處理器的代碼中還可能遇到新的異常,也可以在處理器代碼中拋出異常。
?
●? 實(shí)例
書中提到了實(shí)現(xiàn)一個(gè)大學(xué)人事管理系統(tǒng)框架的實(shí)例,當(dāng)然是一個(gè)很簡(jiǎn)樸的東西,邏輯也不復(fù)雜,不過(guò)其中有些面向?qū)ο缶幊痰某WR(shí)性的知識(shí)和技巧值得一看。我決定照樣子把這段代碼全部都抄過(guò)來(lái),在有價(jià)值的 地方注釋一下。
其基本思路是這樣的:
實(shí)現(xiàn)一個(gè)公共人員的類,包含一些人的基本信息屬性
創(chuàng)建學(xué)生和教職人員兩個(gè)類,分別繼承公共人員類。再根據(jù)學(xué)生和教職人員的屬性不同來(lái)實(shí)現(xiàn)不同的類。
首先是兩個(gè)本例中可能會(huì)用到的異常類型:
class PersonTypeError(TypeError):passclass PersonValueError(ValueError):pass?
然后是公共人員類:
class Person(object): #實(shí)現(xiàn)一個(gè)公共的人員類_num = 0 #用來(lái)記錄創(chuàng)建過(guò)的總?cè)藬?shù)def __init__(self,name,sex,birthday,ident):if not (isinstance(name,str)) and sex in ("女","男"):raise PersonValueErrortry:birth = datetime.date(*birthday)except:raise PersonTypeError("Wrong Date:{0}".format(birthday))self._name = nameself._sex = sexself._id = identself._birthday = birthPerson._num += 1def id(self): return self._iddef name(self): return self._namedef sex(self): return self._sexdef birthday(self): return self._birthdaydef age(self):return datetime.date.today() - self._birthday.yeardef set_name(self,new_name):if not isinstance(new_name,str):raise PersonValueError("Wrong New Name:{0}".format(new_name))self._name = new_name#其他的set方法不一一列舉了,反正都是差不多的def __lt__(self,other):#定義一個(gè)比較魔法方法,當(dāng)兩個(gè)人員對(duì)象比較時(shí)默認(rèn)比較他們的id號(hào)的大小if not isinstance(other,Person):raise PersonTypeError(other)return self._id < other._id@classmethoddef num(cls): #定義一個(gè)獲取目前為止總的注冊(cè)人數(shù)的方法return cls._numdef __str__(self): #定義當(dāng)print此類對(duì)象時(shí)的操作return " ".join((self._id,self._name,self._sex,self._birthday))?
然后是學(xué)生類,學(xué)生類中需要有學(xué)號(hào)這個(gè)id,但是學(xué)號(hào)應(yīng)該有一套生成的規(guī)則,這個(gè)規(guī)則應(yīng)該放在Student這個(gè)類中維護(hù),所以這個(gè)類中應(yīng)該額外加一個(gè)學(xué)號(hào)生成的方法:
class Student(Person):"""學(xué)生類主要考慮增加院系,入學(xué)年份以及課程情況的三種信息"""_id_num = 0@classmethoddef _id_gen(cls):cls._id_num += 1year = datetime.date.today().yearreturn "1{:04}{:05}".format(year, cls._id_num) # 生成一個(gè)類似于 1201300001的學(xué)號(hào),1代表學(xué)生,2013是入學(xué)年份,00001是學(xué)生編號(hào)def __init__(self, name, sex, birthday, depart):Person.__init__(self, name, sex, birthday, self._id_gen())self._department = departself._enroll_year = datetime.date.today().yearself._courses = {}def set_course(self, course): # 模擬選課,選課剛開始還沒有成績(jī)self._courses[course] = Nonedef set_score(self, course, score): #模擬給分if course not in self._courses:raise PersonValueError("The Course is not Selected:{0}".format(course))elif not isinstance(score, float) or score > 100.0 or score < 0.0:raise PersonValueError("Score for Course {0} is Invalid".format(course))self._courses[course] = scoredef scores(self): #獲得一個(gè)學(xué)生全部課程分?jǐn)?shù)情況return [(course,self._courses[course]) for course in self._courses]最后是實(shí)現(xiàn)教職人員的類,教職人員相比于公共人員類要增加院系,員工號(hào),工資等。操作和學(xué)生類是類似的就不再重復(fù)了。只是在它的set_salary方法中我看到了一個(gè)以前沒想到的表達(dá)。。
if type(amount) is not int:xxxxx #這句判斷的意圖在于判斷所給參數(shù)是不是一個(gè)合法的int類型,之前沒想過(guò)直接用is關(guān)鍵字+int類型名就能夠進(jìn)行判斷了。。我之前都是這樣做的: if type(amount) is not type(1):xxxxx轉(zhuǎn)載于:https://www.cnblogs.com/franknihao/p/6890499.html
總結(jié)
以上是生活随笔為你收集整理的【Python数据结构】 抽象数据类型 Python类机制和异常的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 新建Eclipse的web工程目录结构和
- 下一篇: win7的python3.5安装nump