Python基礎之封裝


  一、什么是封裝

  在程序設計中,封裝(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) #得到了新增的功能的值

  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM