一 引子
從封裝本身的意思去理解,封裝就好像是拿來一個麻袋,把青菜,土豆,花菜,還有蘋果一起裝進麻袋,然后把麻袋封上口子。照這種邏輯看,封裝=‘隱藏’,這種理解是相當片面的。
在面向對象中這個麻袋就是你的類或者對象,類或者對象這倆麻袋內部裝了數據屬性和函數屬性,那么對于類和對象來說"封"的概念從何而來,其實封的概念代表隱藏。
在學完了面向對象的類和對象相關的知識后,大家都知道了如何把屬性裝進類或者對象中,那么如何完成封的效果呢?
第一個層面的封裝:類就是麻袋,這本身就是一種封裝
?
第二個層面的封裝:類中定義私有的,只在類的內部使用,外部無法訪問
?
python不依賴語言特性去封裝數據,而是通過遵循一定的數據屬性和函數屬性的命名約定來達到封的效果
約定一:任何一單下劃線開頭的名字都應該是內部的,私有的
# _*_coding:utf-8_*_ __author__ = ' Simon ' class People: __star = ' earth111111111111 ' # 單下劃線開頭的就代表隱藏起來的 __star1 = ' earth111111111111 ' __star2 = ' earth111111111111 ' __star3 = ' earth111111111111 ' def __init__ (self,id,name,age,salary): print ( ' -----> ' ,self. __star ) self.id = id self.name = name self.age = age self.salary = salary def get_id(self): print ( ' 我是私有方法啊,我找到的id是[%s] ' % self.id) # 訪問函數 def get_star(self): print (self. __star ) p1 =People( ' 123123123123 ' , ' alex ' , ' 18 ' ,100000000 ) # print(p1.__star) print (People. __dict__ ) # print(p1.__star) print (p1._People__star) # # p1.get_star() p1.get_star()
python 并不會真的阻止你訪問私有的屬性,模塊也遵循這種約定,如果模塊名以單下劃線開頭,那么from module import *時不能被導入,但是你from module import _private_module 依然是可以導入的
其實很多時候你去調用一個模塊的功能時會遇到單下劃線開頭的
(socket._socket,sys._home,sys._clear_type_cache),這些都是私有的,原則上是供內部調用的,作為外部的你,一意孤行也是可以用的,只不過顯得稍微傻一點。
約定二:雙下劃線開頭的名字
?
第三個層面的封裝:明確區分內外,內部的實現邏輯,外部無法知曉,并且為封裝到內部的邏輯提供一個訪問接口給外部使用(這才是真正的封裝,具體實現,會在面向對象進階中講)
?
二 先看如何隱藏
在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的形式訪問到. #A._A__N是可以訪問到的,
#這種 ,在外部是無法通過__x這個名字訪問到。
?
這種變形需要注意的問題是:
1.這種機制也并沒有真正意義上限制我們從外部直接訪問屬性,知道了類名和屬性名就可以拼出名字:_類名__屬性,然后就可以訪問了,如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
三? 封裝不是單純意義的隱藏
封裝的真諦在于明確地區分內外,封裝的屬性可以直接在內部使用,而不能被外部直接使用,然而定義屬性的目的終歸是要用,外部要想用類隱藏的屬性,需要我們為其開辟接口,讓外部能夠間接地用到我們隱藏起來的屬性,那這么做的意義何在???
1:封裝數據:將數據隱藏起來這不是目的。隱藏起來然后對外提供操作該數據的接口,然后我們可以在接口附加上對該數據操作的限制,以此完成對數據屬性操作的嚴格控制。
class Teacher: def __init__ (self,name,age): # self.__name=name # self.__age=age self.set_info(name,age) def tell_info(self): print ( ' 姓名:%s,年齡:%s ' %(self. __name ,self. __age )) def set_info(self,name,age): if not isinstance(name,str): raise TypeError( ' 姓名必須是字符串類型 ' ) if not isinstance(age,int): raise TypeError( ' 年齡必須是整型 ' ) self. __name = name self. __age = age t =Teacher( ' egon ' ,18 ) t.tell_info() t.set_info( ' egon ' ,19 ) t.tell_info()
2:封裝方法:目的是隔離復雜度
封裝方法舉例:?
1. 你的身體沒有一處不體現著封裝的概念:你的身體把膀胱尿道等等這些尿的功能隱藏了起來,然后為你提供一個尿的接口就可以了(接口就是你的。。。,),你總不能把膀胱掛在身體外面,上廁所的時候就跟別人炫耀:hi,man,你瞅我的膀胱,看看我是怎么尿的。
2. 電視機本身是一個黑盒子,隱藏了所有細節,但是一定會對外提供了一堆按鈕,這些按鈕也正是接口的概念,所以說,封裝并不是單純意義的隱藏!??!
3.?快門就是傻瓜相機為傻瓜們提供的方法,該方法將內部復雜的照相功能都隱藏起來了
提示:在編程語言里,對外提供的接口(接口可理解為了一個入口),可以是函數,稱為接口函數,這與接口的概念還不一樣,接口代表一組接口函數的集合體。
# 取款是功能,而這個功能有很多功能組成:插卡、密碼認證、輸入金額、打印賬單、取錢 # 對使用者來說,只需要知道取款這個功能即可,其余功能我們都可以隱藏起來,很明顯這么做 # 隔離了復雜度,同時也提升了安全性 class ATM: def __card (self): print ( ' 插卡 ' ) def __auth (self): print ( ' 用戶認證 ' ) def __input (self): print ( ' 輸入取款金額 ' ) def __print_bill (self): print ( ' 打印賬單 ' ) def __take_money (self): print ( ' 取款 ' ) def withdraw(self): self. __card () self. __auth () self. __input () self. __print_bill () self. __take_money () a = ATM() a.withdraw() 隔離復雜度的例子
3: 了解
python并不會真的阻止你訪問私有的屬性,模塊也遵循這種約定,如果模塊名以單下劃線開頭,那么from module import *時不能被導入,但是你from module import _private_module依然是可以導入的
其實很多時候你去調用一個模塊的功能時會遇到單下劃線開頭的(socket._socket,sys._home,sys._clear_type_cache),這些都是私有的,原則上是供內部調用的,作為外部的你,一意孤行也是可以用的,只不過顯得稍微傻逼一點點
python要想與其他編程語言一樣,嚴格控制屬性的訪問權限,只能借助內置方法如__getattr__,詳見面向對象進階
四 特性(property)?
什么是特性property
property是一種特殊的屬性,訪問它時會執行一段功能(函數)然后返回值
例一:BMI指數(bmi是計算而來的,但很明顯它聽起來像是一個屬性而非方法,如果我們將其做成一個屬性,更便于理解)
class People: def __init__ (self,name,weight,height): self.name = name self.weight = weight self.height = height @property def bmi(self): return self.weight / (self.height**2 ) p1 =People( ' egon ' ,75,1.85 ) print (p1.bmi)
?
例二:圓的周長和面積?
import math class Circle: def __init__ (self,radius): # 圓的半徑radius self.radius= radius @property def area(self): return math.pi * self.radius**2 # 計算面積 @property def perimeter(self): return 2*math.pi*self.radius # 計算周長 c =Circle(10 ) print (c.radius) print (c.area) # 可以向訪問數據屬性一樣去訪問area,會觸發一個函數的執行,動態計算出一個值 print (c.perimeter) # 同上 ''' 輸出結果: 314.1592653589793 62.83185307179586 '''
#注意:此時的特性arear和perimeter不能被賦值 c.area=3 #為特性area賦值 ''' 拋出異常: AttributeError: can't set attribute '''
為什么要用property
將一個類的函數定義成特性以后,對象再去使用的時候obj.name,根本無法察覺自己的name是執行了一個函數然后計算出來的,這種特性的使用方式 遵循了統一訪問的原則
除此之外,看下
ps:面向對象的封裝有三種方式: 【public】 這種其實就是不封裝,是對外公開的 【protected】 這種封裝方式對外不公開,但對朋友(friend)或者子類(形象的說法是“兒子”,但我不知道為什么大家 不說“女兒”,就像“parent”本來是“父母”的意思,但中文都是叫“父類”)公開 【private】 這種封裝對誰都不公開
python并沒有在語法上把它們三個內建到自己的class機制中,在C++里一般會將所有的所有的數據都設置為私有的,然后提供set和get方法(接口)去設置和獲取,在python中通過property方法可以實現
class Foo: def __init__(self,val): self.__NAME=val #將所有的數據屬性都隱藏起來 @property def name(self): return self.__NAME #obj.name訪問的是self.__NAME(這也是真實值的存放位置) @name.setter def name(self,value): if not isinstance(value,str): #在設定值之前進行類型檢查 raise TypeError('%s must be str' %value) self.__NAME=value #通過類型檢查后,將值value存放到真實的位置self.__NAME @name.deleter def name(self): raise TypeError('Can not delete') f=Foo('egon') print(f.name) # f.name=10 #拋出異常'TypeError: 10 must be str' del f.name #拋出異常'TypeError: Can not delete'
class Foo: def __init__ (self,val): self. __NAME =val # 將所有的數據屬性都隱藏起來 def getname(self): return self. __NAME # obj.name訪問的是self.__NAME(這也是真實值的存放位置) def setname(self,value): if not isinstance(value,str): # 在設定值之前進行類型檢查 raise TypeError( ' %s must be str ' % value) self. __NAME =value # 通過類型檢查后,將值value存放到真實的位置self.__NAME def delname(self): raise TypeError( ' Can not delete ' ) name =property(getname,setname,delname) # 不如裝飾器的方式清晰 了解:一種property的古老用法
五 封裝與擴展性
封裝在于明確區分內外,使得類實現者可以修改封裝內的東西而不影響外部調用者的代碼;而外部使用用者只知道一個接口(函數),只要接口(函數)名、參數不變,使用者的代碼永遠無需改變。這就提供一個良好的合作基礎——或者說,只要接口這個基礎約定不變,則代碼改變不足為慮。
?
# 類的設計者 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 # 類的設計者,輕松的擴展了功能,而類的使用者完全不需要改變自己的代碼 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()
?
六 面向對象的優點
從編程進化論我們得知,面向對象是一種更高等級的結構化編程方式,他的好處有兩點:
1、通過封裝明確了內外,你作為類的締造者,你是上帝,上帝造物的邏輯你無需知道,上帝想讓你知道的你才能知道,這樣就明確了劃分了等級,物就是調用者,上帝就是物的創造者
2、通過繼承+多態在語言層面支持了歸一化設計
注意:不用面向對象語言(即不用class),一樣可以做歸一化(如老掉牙的泛文件概念、游戲行業的一切皆精靈),一樣可以封裝(通過定義模塊和接口),只是用面向對象語言可以直接用語言元素顯示聲明這些而已;而用了面向對象語言,滿篇都是class,并不等于就有了歸一化的設計。甚至,因為被這些花哨的東西迷惑,反而更加不知道什么才是設計。
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
