標籤:python物件導向特性之類的三大特性
一、物件導向簡介
物件導向編程不是python專屬,幾乎所有進階語言都支援;物件導向不管在那個語言中都有三大特性:即:封裝、繼承、多態;具體的本文主要講python物件導向--類及三大特性的具體實現;
二、物件導向之python類特性一:封裝
python通過類實現物件導向的編程;編程大致分為面向過程式的函數式編程,和物件導向編程;
類(Class)和執行個體(Instance)是物件導向最重要的概念。
1、類的簡單定義與使用
class ‘類名‘: 語句塊
如:
class Foo: def bar(self): print("Foo.bar") obj = Foo() #obj即是Foo類的執行個體obj.bar() #通過執行個體去調用調用Foo類中的方法
結果輸出Foo.bar
以上就是最簡單的類定義與使用;
2、self是什嗎?
我們注意到以上的樣本中除了class關鍵字用來定義類,def定義方法(類中的函數我們稱方法,可以理解為類中封裝的函數就是方法)後面有一個self參數,那麼 這個self參數是什嗎?先來一個例子:
class Foo: def bar(self,arg): print(self,arg) z1 = Foo()print(z1)z1.bar(11)
輸出結果:
650) this.width=650;" title="選區_035.png" alt="9aa71c6f354aba5244e37212544fbef4.png-wh_" src="https://s2.51cto.com/oss/201711/17/9aa71c6f354aba5244e37212544fbef4.png-wh_500x0-wm_3-wmp_4-s_1708013327.png" />
以上結果可以看到z1是Foo執行個體列印出記憶體位址和self的記憶體位址一樣,這說明self就代指執行個體本身;
因此self就是類執行個體化出來的對象(執行個體 ),通俗的講,就是那個執行個體對象調用,就代指那個執行個體對象。
3、執行個體出對象
執行個體對象是從類執行個體化而來obj = 類名([參數....]),擁有自己的範圍命名空間,就是擁有自己的記憶體位址空間;同時可以對執行個體對象添加賦值;用於儲存執行個體自己的資料;同時可以調用類中封裝好的方法或資料;
class Foo: def bar(self,arg): print(self.name,self.age,arg)obj = Foo() #執行個體化obj對象obj.name = "san" #向執行個體中添加對象name並賦值obj.age = 18 #向執行個體中添加對象age並賦值obj.bar(666) #調用類方法並傳入參數6666
輸出結果:
(‘san‘, ‘18‘, 666)
從上面的樣本可以看出類在定義時並沒有建立name 和age ;obj執行個體對象建立後通過賦值
讓obj擁有了name和age;因此執行個體obj在調用類中bar方法時可以擷取自身命名空間中name
和age值;實際上執行個體化後的對象如果沒有做出限制(__slots__),可以向對象中儲存添加任意多的值;
4、通過__slots__限制姨執行個體對象添加賦值
通過以上樣本我們瞭解到,在從類執行個體化對象後,可以往對象中添加任意多的值,只要記憶體足夠大;然而任何無節制的增加,都有可能帶來隱患,所以我們通過__slots__功能來限制執行個體對象的賦值;
class Foo: __slots__ = ("name","age") #只允許往執行個體賦name,age def bar(self,arg): print(self.name,self.age,arg) def Other(self): print(self.other) z = Foo() #執行個體化zz.name = "san" #向z對象賦值namez.age = 18 #向z對象賦值agez.bar("6666")z.other = "othter" #向z對象賦值otherz.Other()
運行結果
650) this.width=650;" title="選區_036.png" alt="ea9e37732cb6c62cc58c1570d1a24778.png-wh_" src="https://s5.51cto.com/oss/201711/17/ea9e37732cb6c62cc58c1570d1a24778.png-wh_500x0-wm_3-wmp_4-s_66699157.png" />
這次沒有賦值成功,被限制了提示:“AttributeError: ‘Foo‘ object has no attribute ‘other‘“;這正是我們所要的。
5、建構函式__init__
對於以上的樣本我們往執行個體化後的對象中賦值也可以限制能賦的值,如果要執行個體化出多個類對象,每個類對象有共同的屬性值,如name,age,sex等,那麼我們可以通過建構函式__init__方法來實現,即在執行個體化建立對象時傳入name,age,來達到類似obj.name ="san" obj.age = 18這樣的效果:
class Foo: def __init__(self,name,age): #構造方法,這裡有幾個參數,執行個體化對象時就要傳遞對應的參數 self.name = name self.age = age print("__init__ is start.") #對象執行個體化時自動執行 def foo(self,arg): print(self.name,self.age,arg) obj = Foo("san",18)obj.bar("good")
運行結果
650) this.width=650;" title="選區_037.png" alt="18c3e5747dc3d500e64c039d5759db01.png-wh_" src="https://s2.51cto.com/oss/201711/17/18c3e5747dc3d500e64c039d5759db01.png-wh_500x0-wm_3-wmp_4-s_3206754160.png" />
從運行結果中可以看出 只要執行個體化出對象obj = Foo(["參數...."])時就執行__init__方法中的內容;利用這個功能,我們給各執行個體傳入name,age參數時自動賦值;這就是構造方法的功能;其中參數可選,有無參數在於你設計類時是否需要。
三、類特性二:繼承
繼承民是物件導向的特性之一,即子類(又叫衍生類別)繼承父類(又叫基類或超類)的特性(屬性,方法等);同時子類可以重寫父類中的方法或屬性。說到繼承這時提一嘴,python2.x和python3.x繼承有點區別,python2.x是經典類,通過class 類名(object)變成新式類,python3.x預設是新式類;新式類和經典類的區別在於,多繼承時,繼承順序不一樣;經典類採用”深度優先“方式 尋找;而新式類則是廣度優先去尋找;這裡就不詳述,以下的樣本在python 2.7中所以你會看到類名後有一個object,python3.x下可以不用object。關於深度優先和廣度優先這裡先不詳述,下面有樣本:
1、簡單繼承
樣本:
class F(object): #父類F def f1(self): print("F.f1") def f2(self): print("F.f2")class S(F): #子類S def s1(self): print("s.s1") son = S() #從S類執行個體化對象sonson.s1() #調用S類的s1方法son.f1() #由於在S類中沒有找到f2,所以到父類F中找到f1
運行結果
650) this.width=650;" title="選區_039.png" alt="2fba26d013759d6e4fbd3ae2b784f7e0.png-wh_" src="https://s1.51cto.com/oss/201711/17/2fba26d013759d6e4fbd3ae2b784f7e0.png-wh_500x0-wm_3-wmp_4-s_168259661.png" />
說明:雖然S類中沒有f2方法,但由於S類繼承了F類,而F類中有f1方法,因此可以調用F中的f1方法。
2、重寫
有時候我們在繼承了父類後,父類中的方法可能並不能滿足需求,直接修改父類中的方法又破壞了原類,可能影響到其他引用,此時可以通過在子類中定義一個父類中同名的方法,就可以達到重寫父類方法的目的。
樣本:
class F(object): def f1(self): print("F.f1") def f2(self): print("F.f2")class S(F): def s1(self): print("s.s1") def f2(self): # 不繼承,重寫 print("S.f2")son = S()son.s1() #S類的s1方法son.f1() #繼承F類的f1方法son.f2() #S自己的f2方法,重寫(覆蓋)F類的f2方法
運行結果:
650) this.width=650;" title="選區_040.png" alt="b1fafa511759fe0979af523debe8eb62.png-wh_" src="https://s5.51cto.com/oss/201711/17/b1fafa511759fe0979af523debe8eb62.png-wh_500x0-wm_3-wmp_4-s_3755666789.png" />
3、子類中調用超類方法
雖然上面的例子中重寫了父類中的同名方法,滿足了需求,但能否在重寫的基礎上,引用超類中的方法呢?即先運行超類中的同名方法,再定義自己的同名方法?答案是必須可以啊!
這裡有兩種方法,一起列舉,現實中只用其中的一種,建議使用super:
class F(object): def f1(self): print("F.f1") def f2(self): print("F.f2")class S(F): def s1(self): print("s.s1") def f2(self): # 不繼承,重寫 print("S.f2") super(S,self).f2() #調用父類f2方法一: super(子類,self).父類中的方法(...) F.f2(self) #調用父類f2方法二:父類.方法(self,....) son = S()son.s1() #S類的s1方法son.f1() #繼承F類的f1方法son.f2() #S自己的f2方法,重寫(覆蓋)F類的f2方法
運行結果:
650) this.width=650;" title="選區_041.png" alt="51e043336dc34b0227b62683247ab525.png-wh_" src="https://s3.51cto.com/oss/201711/17/51e043336dc34b0227b62683247ab525.png-wh_500x0-wm_3-wmp_4-s_4272873041.png" />
由於S類中的f2方法除了輸出自己的功能S.f2外還通過兩種調用父類F中的f2方法,所以輸出了兩次F.f2;
注意這裡為了示範只調用了父類中的f2方法,達到繼承和重寫最佳化父類中的f2方法,也可以通過這兩種方法中的一種調用父類中的其他方法;
4、類多繼承
類可以繼承多個類,即在class 類名(父類1,父類2,...)那麼問題來,如果所繼承的類都有一個同名的方法,那調用時如何選擇?上面提到了經典類是深度優先,新式類時廣度優先,本文不做深度優先和廣度優先尋找比較,只講新式類的廣度優先;有興趣的可以自行尋找資料。
樣本:
class Base(object): def a(self): print("Base.a")class F1(Base): def a1(self): print(‘F1.a‘) def a11(self): print("F1.a11") class F2(Base): def a2(self): print(‘F2.a‘) def a11(self): print("F2.a11")class S(F1,F2): passclass S2(F2,F1): passobj = S()obj.a11()obj2 = S2()obj2.a11()
運行結果:
650) this.width=650;" title="選區_042.png" alt="d6e7b907756a11b080b43873044109ff.png-wh_" src="https://s5.51cto.com/oss/201711/17/d6e7b907756a11b080b43873044109ff.png-wh_500x0-wm_3-wmp_4-s_2176283826.png" />
obj繼承順序是F1,F2 結果是F1.a11
obj2繼承順序是F2,F1 結果是F2.all
5、有交集的多繼承尋找
obj對象從A類執行個體化而來,A類繼承了B和C類,B類繼承D,C類也繼承D類;baba方法在C類和D類中都有,此時obj.baba()方法結果是怎樣?
樣本:
class D: def bar(self): print(‘D.bar‘) def baba(self): print("D.baba")class C(D): def bar(self): print(‘C.bar‘) def baba(self): print("C.baba")class B(D): def bar(self): print(‘B.bar‘)class A(B, C): def bar(self): print(‘A.bar‘)obj = A()obj.bar()obj.baba()
運行結果:
650) this.width=650;" title="選區_045.png" alt="240e625fda7accf24feb8d18c15590c5.png-wh_" src="https://s1.51cto.com/oss/201711/17/240e625fda7accf24feb8d18c15590c5.png-wh_500x0-wm_3-wmp_4-s_993808898.png" />
# 執行bar和baba方法時
# 首先去A類中尋找,如果A類中沒有,則繼續去B類中找,如果B類中麼有,則繼續去C類中找,如果C類中麼有,則繼續去D類中找,如果還是未找到,則報錯
# 所以,尋找順序:A --> B --> C --> D
# 在上述尋找bar方法的過程中,一旦找到,則尋找過程立即中斷,便不會再繼續找了
同樣的例子我們調回python2.7作為經典類執行,看結果。
650) this.width=650;" title="選區_046.png" alt="2a17181b8338f0c29f30416761f2e9df.png-wh_" src="https://s4.51cto.com/oss/201711/17/2a17181b8338f0c29f30416761f2e9df.png-wh_500x0-wm_3-wmp_4-s_2383644878.png" />
執行bar和baba方法時
首先去A類中尋找,如果A類中沒有,則繼續去B類中找,如果B類中麼有,則繼續去D類中找,如果D類中麼有,則繼續去C類中找,如果還是未找到,則報錯
所以,尋找順序:A --> B --> D --> C
在上述尋找bar方法的過程中,一旦找到,則尋找過程立即中斷,便不會再繼續找了
這裡的baba方法在D中找到,所以顯示為D.baba
深度優先和廣度優先尋找順序
650) this.width=650;" title="QQ20171117152450.png" alt="67efcf62e4f16c675b9552340a4f0194.png-wh_" src="https://s5.51cto.com/oss/201711/17/67efcf62e4f16c675b9552340a4f0194.png-wh_500x0-wm_3-wmp_4-s_4162088720.png" />
6、多繼承之__init__執行順序
以下是一個類比實踐中多繼承情況下__init__構造方法的執行,有利於我們閱讀項目源碼。
樣本:
class BaseRequest: #(3) def __init__(self): print("BaseRequest.init")class RequestHandler(BaseRequest): #(2) def __init__(self): print("RequestHandler.init") BaseRequest.__init__(self) #調用父類的__init__方法 # super(RequestHandler,self).__init__() def serve_forever(self): #(5) print("RequestHandler.serve_forever") self.process_request() def process_request(self): print(‘RequestHandler.process_request‘)class Minx: def process_request(self): #(6) print("Minx.process_request")class son(Minx,RequestHandler): passobj = son() #(1)obj.serve_forever() #(4)
以上的注釋後的數字是執行的流程。obj = son()的作用:
執行個體化出obj對象;
執行__init__構造方法,在多繼承環境 下,和上面講的調用其他類方面尋找一致,首先尋找 son類中的__init__如果沒有按新式類的方度優先順序尋找所繼承類中的__init__,這裡首先找到Minx,沒有,再找到RequestHandler類,執行列印“RequestHandler.init”,裡的__init__再次執行了父類中的__init__方法,因此又列印出"BaseRequest.init";
執行obj.serve_forever()時,同樣的尋找,列印出“RequestHandler.serve_forever”,注意這裡又調用 了self.process_request()方法,由於self指的就是所調用的對象obj即obj.process_request()方法,因此重新從son(Minx,RequestHandler)中按順序尋找,而不是RequestHandler類下的 process_reques方法。因此找到Minx下的process_request方法,得到結果:“Minx.process_request”
執行結果如下:
650) this.width=650;" title="選區_047.png" alt="43461690e1de0c056789b9529534ec27.png-wh_" src="https://s3.51cto.com/oss/201711/17/43461690e1de0c056789b9529534ec27.png-wh_500x0-wm_3-wmp_4-s_1302415823.png" />
四、python面向對像之多態
多態簡單的理解就是同一個方法在不現的對象調用時顯示出的效果不一樣,如+ 在整形數字相加時是數學運算,在字串相加時加是串連符;
python的物件導向原生支援多態,不像java等強型別語言,傳遞參數時必須指定類型,而python沒有此此限制,這也是python原生動支援多態的原因之一。
本文主要說明了python物件導向的三大特性,封裝,繼承及多態,繼承有多繼承,新式繼承與經典類繼承的區別。個人總結,如有不當之處歡迎指正。
本文出自 “學無止境,學以致用” 部落格,請務必保留此出處http://dyc2005.blog.51cto.com/270872/1982808
python物件導向OOP入門