1、什麼是封裝
在程序設計中,封裝(Encapsulation)是對具體對象的一種抽象,即將某些部分隱藏起來,在程序外部看不到,其html
含義是其餘程序沒法調用。python
要了解封裝,離不開「私有化」,就是將類或者是函數中的某些屬性限制在某個區域以內,外部沒法調用。編程
2、爲何要封裝
封裝數據的主要緣由是:保護隱私(把不想別人知道的東西封裝起來)編程語言
封裝方法的主要緣由是:隔離複雜度(好比:電視機,咱們看見的就是一個黑匣子,其實裏面有不少電器元件,對於函數
用戶來講,咱們不須要清楚裏面都有些元件,電視機把那些電器元件封裝在黑匣子裏,提供給用戶的只是幾個按鈕接口,post
經過按鈕就能實現對電視機的操做。)url
提示:在編程語言裏,對外提供的接口(接口可理解爲了一個入口),就是函數,稱爲接口函數,這與接口的概念還spa
不同,接口表明一組接口函數的集合體。設計
3、封裝分爲兩個層面
封裝其實分爲兩個層面,但不管哪一種層面的封裝,都要對外界提供好訪問你內部隱藏內容的接口(接口能夠理解爲入code
口,有了這個入口,使用者無需且不可以直接訪問到內部隱藏的細節,只能走接口,而且咱們能夠在接口的實現上附加更
多的處理邏輯,從而嚴格控制使用者的訪問)
第一個層面的封裝(什麼都不用作):建立類和對象會分別建立兩者的名稱空間,咱們只能用類名.或者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的形式訪問到.
|
這種自動變形的特色:
一、類中定義的__x只能在內部使用,如self.__x,引用的就是變形的結果。
二、這種變形其實正是針對外部的變形,在外部是沒法經過__x這個名字訪問到的。
三、在子類定義的__x不會覆蓋在父類定義的__x,由於子類中變造成了:_子類名__x,而父類中變造成了:_父
類名__x,即雙下滑線開頭的屬性在繼承給子類時,子類是沒法覆蓋的。
注意:對於這一層面的封裝(隱藏),咱們須要在類中定義一個函數(接口函數)在它內部訪問被隱藏的屬性,而後
外部就可使用了
這種變形須要注意的問題是:
一、這種機制也並無真正意義上限制咱們從外部直接訪問屬性,知道了類名和屬性名就能夠拼出名字:_類名__屬
性,而後就能夠訪問了,如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
|
二、變形的過程只在類的定義是發生一次,在定義後的賦值操做,不會變形
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並無變形
|
三、在繼承中,父類若是不想讓子類覆蓋本身的方法,能夠將方法定義爲私有的
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
|
4、特性(property)
一、什麼是特性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
'''
|
二、爲何要用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方法能夠實現。
5、封裝與擴展性
封裝在於明確區份內外,使得類實現者能夠修改封裝內的東西而不影響外部調用者的代碼;而外部使用用者只知道一
個接口(函數),只要接口(函數)名、參數不變,使用者的代碼永遠無需改變。這就提供一個良好的合做基礎——或者說
,只要接口這個基礎約定不變,則代碼改變不足爲慮。
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
)
#獲得了新增的功能的值
|