1. 程式人生 > >python面向對象三大特性之一封裝

python面向對象三大特性之一封裝

pac 修改 不變 containe erro delet 異常 內部 自己的

一、什麽是封裝

  在程序設計中,封裝(Encapsulation)是對具體對象的一種抽象,即將某些部分隱藏起來,在程序外部看不到,其

含義是其他程序無法調用。

  要了解封裝,離不開“私有化”,就是將類或者是函數中的某些屬性限制在某個區域之內,外部無法調用。

  二、為什麽要封裝

  封裝數據的主要原因是:保護隱私(把不想別人知道的東西封裝起來)

  封裝方法的主要原因是:隔離復雜度(比如:電視機,我們看見的就是一個黑匣子,其實裏面有很多電器元件,對於

用戶來說,我們不需要清楚裏面都有些元件,電視機把那些電器元件封裝在黑匣子裏,提供給用戶的只是幾個按鈕接口,

通過按鈕就能實現對電視機的操作。)

  提示:在編程語言裏,對外提供的接口(接口可理解為了一個入口),就是函數,稱為接口函數,這與接口的概念還

不一樣,接口代表一組接口函數的集合體。

  三、封裝分為兩個層面

  封裝其實分為兩個層面,但無論哪種層面的封裝,都要對外界提供好訪問你內部隱藏內容的接口(接口可以理解為入

口,有了這個入口,使用者無需且不能夠直接訪問到內部隱藏的細節,只能走接口,並且我們可以在接口的實現上附加更

多的處理邏輯,從而嚴格控制使用者的訪問)

  第一個層面的封裝(什麽都不用做):創建類和對象會分別創建二者的名稱空間,我們只能用類名.或者obj.的方式去

訪問裏面的名字,這本身就是一種封裝。

1 2 3 4 5 print(m1.brand) #實例化對象(m1.) print(motor_vehicle.tag) #類名(motor_vehicle.) -------------輸出結果-------------- 春風 fuel oil

  註意:對於這一層面的封裝(隱藏),類名.和實例名.就是訪問隱藏屬性的接口

  第二個層面的封裝:類中把某些屬性和方法隱藏起來(或者說定義成私有的),只在類的內部使用、外部無法訪問,或

者留下少量接口(函數)供外部訪問。

  Python中私有化的方法也比較簡單,即在準備私有化的屬性(包括方法、數據)名字前面加兩個下劃線即可。

  類中所有雙下劃線開頭的名稱如__x都會自動變形成:_類名__x的形式:

1 2 3 4 5 6 7 8 class A: __N=0 #類的數據屬性就應該是共享的,但是語法上是可以把類的數據屬性設置成私有的如__N,會變形為_A__N def __init__(self): self.__X=10 #變形為self._A__X def __foo(self): #變形為_A__foo print(‘from A‘) def bar(self): self.__foo() #只有在類內部才可以通過__foo的形式訪問到.  

  這種自動變形的特點:

    1、類中定義的__x只能在內部使用,如self.__x,引用的就是變形的結果。

    2、這種變形其實正是針對外部的變形,在外部是無法通過__x這個名字訪問到的。

    3、在子類定義的__x不會覆蓋在父類定義的__x,因為子類中變形成了:_子類名__x,而父類中變形成了:_父

類名__x,即雙下滑線開頭的屬性在繼承給子類時,子類是無法覆蓋的。

  註意:對於這一層面的封裝(隱藏),我們需要在類中定義一個函數(接口函數)在它內部訪問被隱藏的屬性,然後

外部就可以使用了

  這種變形需要註意的問題是:

  1、這種機制也並沒有真正意義上限制我們從外部直接訪問屬性,知道了類名和屬性名就可以拼出名字:_類名__屬

性,然後就可以訪問了,如a._A__N

1 2 3 4 5 6 7 8 a = A() print(a._A__N) print(a._A__X) print(A._A__N) --------輸出結果-------- 0 10 0

  2、變形的過程只在類的定義是發生一次,在定義後的賦值操作,不會變形

1 2 3 4 5 6 7 a = A() #實例化對象a print(a.__dict__) #打印變形的內容 a.__Y = 20 #新增Y的值,此時加__不會變形 print(a.__dict__) #打印變形的內容 ---------輸出結果---------- {‘_A__X‘: 10} {‘_A__X‘: 10, ‘__Y‘: 20} #發現後面的Y並沒有變形

  3、在繼承中,父類如果不想讓子類覆蓋自己的方法,可以將方法定義為私有的

1 2 3 4 5 6 7 8 9 10 11 12 13 14 class A: #這是正常情況 def fa(self): print("from A") def test(self): self.fa() class B(A): def fa(self): print("from B") b = B() b.test() --------輸出結果---------- from B

  看一下把fa被定義成私有的情況:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 class A: #把fa定義成私有的,即__fa def __fa(self): #在定義時就變形為_A__fa print("from A") def test(self): self.__fa() #只會與自己所在的類為準,即調用_A__fa class B(A): def __fa(self): #b調用的是test,跟這個沒關系 print("from B") b = B() b.test() -------輸出結果--------- from A

  四、特性(property)

  1、什麽是特性property

  property是一種特殊的屬性,訪問它時會執行一段功能(函數)然後返回值(就是一個裝飾器)

  註意:被property裝飾的屬性會優先於對象的屬性被使用,而被propery裝飾的屬性,分成三種:property、被裝飾

的函數名.setter、被裝飾的函數名.deleter(都是以裝飾器的形式)。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class room: #定義一個房間的類 def __init__(self,length,width,high): self.length = length #房間的長 self.width = width #房間的寬 self.high = high #房間的高 @property def area(self): #求房間的平方的功能 return self.length * self.width #房間的面積就是:長x寬 @property def perimeter(self): #求房間的周長的功能 return 2 * (self.length + self.width) #公式為:(長 + 寬)x 2 @property def volume(self): #求房間的體積的功能 return self.length * self.width * self.high #公式為:長 x 寬 x 高 r1 = room(2,3,4) #實例化一個對象r1 print("r1.area:",r1.area) #可以像訪問數據屬性一樣去訪問area,會觸發一個函數的執行,動態計算出一個值 print("r1.perimeter:",r1.perimeter) #同上,就不用像調用綁定方法一樣,還得加括號,才能運行 print("r1.volume:",r1.volume) #同上,就像是把運算過程封裝到一個函數內部,我們不管過程,只要有結果就行 ------------輸出結果--------------- r1.area: 6 r1.perimeter: 10 r1.volume: 24

  註意:此時的特性arear、perimeter和volume不能被賦值。

1 2 3 4 5 6 7 8 9 r1.area = 8 #為特性area賦值 r1.perimeter = 14 #為特性perimeter賦值 r1.volume = 24 #為特性volume賦值 ‘‘‘ 拋出異常: r1.area = 8 #第一個就拋異常了,後面的也一樣 AttributeError: can‘t set attribute ‘‘‘

2、為什麽要用property

  將一個類的函數定義成特性以後,對象再去使用的時候obj.name,根本無法察覺自己的name是執行了一個函數然後

計算出來的,這種特性的使用方式遵循了統一訪問的原則。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class people: #定義一個人的類 def __init__(self,name,sex): self.name = name self.sex = sex #p1.sex = "male",遇到property,優先用property @property #查看sex的值 def sex(self): return self.__sex #返回正真存值的地方 @sex.setter #修改sex的值 def sex(self,value): if not isinstance(value,str): #在設定值之前進行類型檢查 raise TypeError("性別必須是字符串類型") #不是str類型時,主動拋出異常 self.__sex = value #類型正確的時候,直接修改__sex的值,這是值正真存放的地方 #這裏sex前加"__",對sex變形,隱藏。 @sex.deleter #刪除sex def sex(self): del self.__sex p1 = people("egon","male") #實例化對象p1 print(p1.sex) #查看p1的sex,此時要註意self.sex的優先級 p1.sex = "female" #修改sex的值 print(p1.sex) #查看修改後p1的sex print(p1.__dict__) #查看p1的名稱空間,此時裏面有sex del p1.sex #刪除p1的sex print(p1.__dict__) #查看p1的名稱空間,此時發現裏面已經沒有sex了 -------------------輸出結果-------------------- male female {‘name‘: ‘egon‘, ‘_people__sex‘: ‘female‘} {‘name‘: ‘egon‘}

  python並沒有在語法上把它們三個內建到自己的class機制中,在C++裏一般會將所有的所有的數據都設置為私有的

,然後提供set和get方法(接口)去設置和獲取,在python中通過property方法可以實現。

  五、封裝與擴展性

  封裝在於明確區分內外,使得類實現者可以修改封裝內的東西而不影響外部調用者的代碼;而外部使用用者只知道一

個接口(函數),只要接口(函數)名、參數不變,使用者的代碼永遠無需改變。這就提供一個良好的合作基礎——或者說

,只要接口這個基礎約定不變,則代碼改變不足為慮。

1 2 3 4 5 6 7 8 9 10 11 12 #類的設計者 class room: #定義一個房間的類 def __init__(self,name,owner,length,width,high): self.name = name self.owner = owner self.__length = length #房間的長 self.__width = width #房間的寬 self.__high = high #房間的高 @property def area(self): #求房間的平方的功能 return self.__length * self.__width #對外提供的接口,隱藏了內部的實現細節,\ # 此時我們想求的是房間的面積就是:長x寬

  實例化對象通過接口,調用相關屬性得到想要的值:

1 2 3 4 5 #類的使用者 r1 = room("客廳","michael",20,30,9) #實例化一個對象r1 print(r1.area) #通過接口使用(area),使用者得到了客廳的面積 -------------輸出結果-------------- 600 #得到了客廳的面積

  擴展原有的代碼,使功能增加:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 #類的設計者,輕松的擴展了功能,而類的使用者完全不需要改變自己的代碼 class room: #定義一個房間的類 def __init__(self,name,owner,length,width,high): self.name = name #房間名 self.owner = owner #房子的主人 self.__length = length #房間的長 self.__width = width #房間的寬 self.__high = high #房間的高 @property def area(self): #對外提供的接口,隱藏內部實現 return self.__length * self.__width,\ self.__length * self.__width * self.__high #此時我們增加了求體積, # 內部邏輯變了,只需增加這行代碼就能簡單實現,而且外部調用感知不到,仍然使 # 用該方法,但是功能已經增加了

  對於類的使用者,仍然在調用area接口的人來說,根本無需改動自己的代碼,就可以用上新功能:

1 2 3 4 5 #類的使用者 r1 = room("客廳","michael",20,30,9) #實例化一個對象r1 print(r1.area) #通過接口使用(area),使用者得到了客廳的面積 --------------輸出結果--------------- (600, 5400) #得到了新增的功能的值

python面向對象三大特性之一封裝