python面向對象三大特性之一封裝
一、什麽是封裝
在程序設計中,封裝(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面向對象三大特性之一封裝