1. 程式人生 > >python基礎----封裝、封裝與擴充套件性

python基礎----封裝、封裝與擴充套件性

要封裝什麼

你錢包的有多少錢(資料的封裝)

你的性取向(資料的封裝)

你撒尿的具體功能是怎麼實現的(方法的封裝)

為什麼要封裝

封裝資料的主要原因是:保護隱私

封裝方法的主要原因是:隔離複雜度

提示:在程式語言裡,對外提供的介面(介面可理解為了一個入口),就是函式,稱為介面函式,這與介面的概念還不一樣,介面代表一組介面函式的集合體。

封裝分為兩個層面

封裝其實分為兩個層面,但無論哪種層面的封裝,都要對外界提供好訪問你內部隱藏內容的介面(介面可以理解為入口,有了這個入口,使用者無需且不能夠直接訪問到內部隱藏的細節,只能走介面,並且我們可以在介面的實現上附加更多的處理邏輯,從而嚴格控制使用者的訪問)

第一個層面的封裝(什麼都不用做):建立類和物件會分別建立二者的名稱空間,我們只能用類名.或者obj.的方式去訪問裡面的名字,這本身就是一種封裝

>>> r1.nickname
'草叢倫'
>>> Riven.camp
'Noxus'

注意:對於這一層面的封裝(隱藏),類名.和例項名.就是訪問隱藏屬性的介面

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

在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()
>>> a._A__N
>>> a._A__X
>>> A._A__N

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

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定義成私有的,即__fa
>>> class A:
...     def __fa(self): #在定義時就變形為_A__fa
...         print('from A')
...     def test(self):
...         self.__fa() #只會與自己所在的類為準,即呼叫_A__fa
... 
>>> class B(A):
...     def __fa(self):
...         print('from B')
... 
>>> b=B()
>>> b.test()
from A

python並不會真的阻止你訪問私有的屬性,模組也遵循這種約定,如果模組名以單下劃線開頭,那麼from module import *時不能被匯入,但是你from module import _private_module依然是可以匯入

其實很多時候你去呼叫一個模組的功能時會遇到單下劃線開頭的(socket._socket,sys._home,sys._clear_type_cache),這些都是私有的,原則上是供內部呼叫的,作為外部的你,一意孤行也是可以用的,只不過顯得稍微傻逼一點點

python要想與其他程式語言一樣,嚴格控制屬性的訪問許可權,只能藉助內建方法如__getattr__,詳見面向物件進階.

課堂筆記:

class A:
    __x=1    #_A__x
    def __test(self):   #_A__test
        print('from A')

print(A.__x)
print(A._A__x)
a=A()
print(a._A__x)
print(A.__dict__)
A._A__test(123)

a=A()
a._A__test()




# __名字,這種語法,只在定義的時候才會有變形的效果,如果類或者物件已經產生了,就不會有變形效果

class B:
    pass

B.__x=1
print(B.__dict__)
print(B.__x)

b=B()
b.__x=1
print(b.__dict__)
print(b.__x)




#在定義階段就會變形
class A:
    def __fa(self):     #_A__fa
        print('from A')
    def test(self):
        self.__fa()      #self._A__fa

class B(A):
    def __fa(self):    #_B__fa
        print('from B')
b=B()
b.test()




class A:
    def __init__(self):
        self.__x=1
    def tell(self):
        print(self.__x)          # self.__x    在類內部就可以直接用__名字來訪問到變形的屬性

a=A()
print(a.__dict__)
print(a.__x)
a.tell()

封裝與擴充套件性

封裝在於明確區分內外,使得類實現者可以修改封裝內的東西而不影響外部呼叫者的程式碼;而外部使用者只知道一個介面(函式),只要介面(函式)名、引數不變,使用者的程式碼永遠無需改變。這就提供一個良好的合作基礎——或者說,只要介面這個基礎約定不變,則程式碼改變不足為慮。

#類的設計者
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self): #對外提供的介面,隱藏了內部的實現細節,此時我們想求的是面積
        return self.__width * self.__length
#使用者
>>> r1=Room('臥室','egon',20,20,20)
>>> r1.tell_area() #使用者呼叫介面tell_area
400
#類的設計者,輕鬆的擴充套件了功能,而類的使用者完全不需要改變自己的程式碼
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self): #對外提供的介面,隱藏內部實現,此時我們想求的是體積,內部邏輯變了,只需求修該下列一行就可以很簡答的實現,而且外部呼叫感知不到,仍然使用該方法,但是功能已經變了
        return self.__width * self.__length * self.__high

對於仍然在使用tell_area介面的人來說,根本無需改動自己的程式碼,就可以用上新功能

>>> r1.tell_area()
8000