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