Python面向對象編程——封裝
從封裝本身的意思去理解,封裝就好像是拿來一個麻袋,把小貓,小狗,小王八,還有alex一起裝進麻袋,然后把麻袋封上口子。照這種邏輯看,封裝=‘隱藏’,這種理解是相當片面的。
一、先看如何隱藏
在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是可以訪問到的,即這種操作并不是嚴格意義上的限制外部訪問,僅僅只是一種語法意義上的變形
這種自動變形的特點:
1、類中定義的
__x
只能在內部使用,如
self.__x
,引用的就是變形的結果。
2、這種變形其實正是針對外部的變形,在外部是無法通過
__x
這個名字訪問到的。
3、在子類定義的
__x
不會覆蓋在父類定義的
__x
,因為子類中變形成了:
_子類名__x
,而父類中變形成了:
_父類名__x
,即雙下滑線開頭的屬性在繼承給子類時,子類是無法覆蓋的。
這種變形需要注意的問題是:
1、這種機制也并沒有真正意義上限制我們從外部直接訪問屬性,知道了類名和屬性名就可以拼出名字:
_類名__屬性
,然后就可以訪問了,如
a._A__N
。
2、變形的過程只在類的定義是發(fā)生一次,在定義后的賦值操作,不會變形。
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
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、封裝方法:目的是隔離復雜度
#取款是功能,而這個功能有很多功能組成:插卡、密碼認證、輸入金額、打印賬單、取錢
#對使用者來說,只需要知道取款這個功能即可,其余功能我們都可以隱藏起來,很明顯這么做
#隔離了復雜度,同時也提升了安全性
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()
封裝方法的其他舉例:
1、你的身體沒有一處不體現著封裝的概念:你的身體把膀胱尿道等等這些尿的功能隱藏了起來,然后為你提供一個尿的接口就可以了(接口就是你的。。。,),你總不能把膀胱掛在身體外面,上廁所的時候就跟別人炫耀:hi,man,你瞅我的膀胱,看看我是怎么尿的。
2、電視機本身是一個黑盒子,隱藏了所有細節(jié),但是一定會對外提供了一堆按鈕,這些按鈕也正是接口的概念,所以說,封裝并不是單純意義的隱藏?。。?
3、快門就是傻瓜相機為傻瓜們提供的方法,該方法將內部復雜的照相功能都隱藏起來了。
注:在編程語言里,對外提供的接口(接口可理解為了一個入口),可以是函數,稱為接口函數,這與接口的概念還不一樣,接口代表一組接口函數的集合體。
三、特性(property)
1、什么是特性property
property是一種特殊的屬性,訪問它時會執(zhí)行一段功能(函數)然后返回值。
例一:BMI指數(bmi是計算而來的,但很明顯它聽起來像是一個屬性而非方法,如果我們將其做成一個屬性,更便于理解)。
成人的BMI數值:
過輕:低于18.5
正常:18.5-23.9
過重:24-27
肥胖:28-32
非常肥胖, 高于32
體質指數(BMI)=體重(kg)÷身高^2(m)
EX:70kg÷(1.75×1.75)=22.86
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,會觸發(fā)一個函數的執(zhí)行,動態(tài)計算出一個值
print(c.perimeter) #同上
'''
輸出結果:
314.1592653589793
62.83185307179586
'''
注:此時的特性area和perimeter不能被賦值
c.area=3 #為特性area賦值
'''
拋出異常:
AttributeError: can't set attribute
'''
2、為什么要用property
將一個類的函數定義成特性以后,對象再去使用的時候obj.name,根本無法察覺自己的name是執(zhí)行了一個函數然后計算出來的,這種特性的使用方式 遵循了統(tǒng)一訪問的原則
除此之外,看下
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'
四、封裝與可擴展性
封裝在于明確區(qū)分內外,使得類實現者可以修改封裝內的東西而不影響外部調用者的代碼;而外部使用用者只知道一個接口(函數),只要接口(函數)名、參數不變,使用者的代碼永遠無需改變。這就提供一個良好的合作基礎——或者說,只要接口這個基礎約定不變,則代碼改變不足為慮。
#類的設計者
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): #對外提供的接口,隱藏了內部的實現細節(jié),此時我們想求的是面積
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()
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

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