從封裝自己的意思去理解,封裝就好像是拿來一個麻袋,把小貓,小狗,小王八,還有egon和alex一塊兒裝進麻袋,而後把麻袋封上口子。但其實這種理解至關片面python
首先咱們要了解編程
你錢包的有多少錢(數據的封裝)socket
你的性取向(數據的封裝)編程語言
你撒尿的具體功能是怎麼實現的(方法的封裝)ide
封裝數據的主要緣由是:保護隱私(做爲男人的你,臉上就寫着:我喜歡男人,你懼怕麼?)函數
封裝方法的主要緣由是:隔離複雜度(快門就是傻瓜相機爲傻瓜們提供的方法,該方法將內部複雜的照相功能都隱藏起來了,好比你沒必要知道你本身的尿是怎麼流出來的,你直接掏出本身的接口就能用尿這個功能)spa
你的身體沒有一處不體現着封裝的概念:你的身體把膀胱尿道等等這些尿的功能隱藏了起來,而後爲你提供一個尿的接口就能夠了(接口就是你的。。。,),你總不能把膀胱掛在身體外面,上廁所的時候就跟別人炫耀:hi,man,你瞅個人膀胱,看看我是怎麼尿的。還有你的頭把你的腦子封裝到了腦袋裏,而後提供了眼睛這個接口....設計
提示:在編程語言裏,對外提供的接口(接口可理解爲了一個入口),就是函數,稱爲接口函數,這與接口的概念還不同,接口表明一組接口函數的集合體。code
封裝其實分爲兩個層面,但不管哪一種層面的封裝,都要對外界提供好訪問你內部隱藏內容的接口(接口能夠理解爲入口,有了這個入口,使用者無需且不可以直接訪問到內部隱藏的細節,只能走接口,而且咱們能夠在接口的實現上附加更多的處理邏輯,從而嚴格控制使用者的訪問)對象
第一個層面的封裝(什麼都不用作):建立類和對象會分別建立兩者的名稱空間,咱們只能用類名.或者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__) 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