一、什么是封裝
在程序設計中,封裝(Encapsulation)是對具體對象的一種抽象,即將某些部分隱藏起來,在程序外部看不到,其
含義是其他程序無法調用。
要了解封裝,離不開“私有化”,就是將類或者是函數中的某些屬性限制在某個區域之內,外部無法調用。
二、為什么要封裝
封裝數據的主要原因是:保護隱私(把不想別人知道的東西封裝起來)
封裝方法的主要原因是:隔離復雜度(比如:電視機,我們看見的就是一個黑匣子,其實里面有很多電器元件,對於
用戶來說,我們不需要清楚里面都有些元件,電視機把那些電器元件封裝在黑匣子里,提供給用戶的只是幾個按鈕接口,
通過按鈕就能實現對電視機的操作。)
提示:在編程語言里,對外提供的接口(接口可理解為了一個入口),就是函數,稱為接口函數,這與接口的概念還
不一樣,接口代表一組接口函數的集合體。
三、封裝分為兩個層面
封裝其實分為兩個層面,但無論哪種層面的封裝,都要對外界提供好訪問你內部隱藏內容的接口(接口可以理解為入
口,有了這個入口,使用者無需且不能夠直接訪問到內部隱藏的細節,只能走接口,並且我們可以在接口的實現上附加更
多的處理邏輯,從而嚴格控制使用者的訪問)
第一個層面的封裝(什么都不用做):創建類和對象會分別創建二者的名稱空間,我們只能用類名.或者obj.的方式去
訪問里面的名字,這本身就是一種封裝。
print(m1.brand) #實例化對象(m1.) print(motor_vehicle.tag) #類名(motor_vehicle.) -------------輸出結果-------------- 春風 fuel oil
注意:對於這一層面的封裝(隱藏),類名.和實例名.就是訪問隱藏屬性的接口
第二個層面的封裝:類中把某些屬性和方法隱藏起來(或者說定義成私有的),只在類的內部使用、外部無法訪問,或
者留下少量接口(函數)供外部訪問。
Python中私有化的方法也比較簡單,即在准備私有化的屬性(包括方法、數據)名字前面加兩個下划線即可。
類中所有雙下划線開頭的名稱如__x都會自動變形成:_類名__x的形式:
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
a = A() print(a._A__N) print(a._A__X) print(A._A__N) --------輸出結果-------- 0 10 0
2、變形的過程只在類的定義是發生一次,在定義后的賦值操作,不會變形
a = A() #實例化對象a
print(a.__dict__) #打印變形的內容
a.__Y = 20 #新增Y的值,此時加__不會變形
print(a.__dict__) #打印變形的內容
---------輸出結果----------
{'_A__X': 10}
{'_A__X': 10, '__Y': 20} #發現后面的Y並沒有變形
3、在繼承中,父類如果不想讓子類覆蓋自己的方法,可以將方法定義為私有的
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被定義成私有的情況:
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(都是以裝飾器的形式)。
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不能被賦值。
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是執行了一個函數然后
計算出來的,這種特性的使用方式遵循了統一訪問的原則。
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方法可以實現。
五、封裝與擴展性
封裝在於明確區分內外,使得類實現者可以修改封裝內的東西而不影響外部調用者的代碼;而外部使用用者只知道一
個接口(函數),只要接口(函數)名、參數不變,使用者的代碼永遠無需改變。這就提供一個良好的合作基礎——或者說
,只要接口這個基礎約定不變,則代碼改變不足為慮。
#類的設計者
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寬
實例化對象通過接口,調用相關屬性得到想要的值:
#類的使用者
r1 = room("客廳","michael",20,30,9) #實例化一個對象r1
print(r1.area) #通過接口使用(area),使用者得到了客廳的面積
-------------輸出結果--------------
600 #得到了客廳的面積
擴展原有的代碼,使功能增加:
#類的設計者,輕松的擴展了功能,而類的使用者完全不需要改變自己的代碼
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接口的人來說,根本無需改動自己的代碼,就可以用上新功能:
#類的使用者
r1 = room("客廳","michael",20,30,9) #實例化一個對象r1
print(r1.area) #通過接口使用(area),使用者得到了客廳的面積
--------------輸出結果---------------
(600, 5400) #得到了新增的功能的值
