python中的封装调用_Python基础之封装
一、什么是封裝
在程序設(shè)計(jì)中,封裝(Encapsulation)是對(duì)具體對(duì)象的一種抽象,即將某些部分隱藏起來(lái),在程序外部看不到,其
含義是其他程序無(wú)法調(diào)用。
要了解封裝,離不開(kāi)“私有化”,就是將類(lèi)或者是函數(shù)中的某些屬性限制在某個(gè)區(qū)域之內(nèi),外部無(wú)法調(diào)用。
二、為什么要封裝
封裝數(shù)據(jù)的主要原因是:保護(hù)隱私(把不想別人知道的東西封裝起來(lái))
封裝方法的主要原因是:隔離復(fù)雜度(比如:電視機(jī),我們看見(jiàn)的就是一個(gè)黑匣子,其實(shí)里面有很多電器元件,對(duì)于
用戶(hù)來(lái)說(shuō),我們不需要清楚里面都有些元件,電視機(jī)把那些電器元件封裝在黑匣子里,提供給用戶(hù)的只是幾個(gè)按鈕接口,
通過(guò)按鈕就能實(shí)現(xiàn)對(duì)電視機(jī)的操作。)
提示:在編程語(yǔ)言里,對(duì)外提供的接口(接口可理解為了一個(gè)入口),就是函數(shù),稱(chēng)為接口函數(shù),這與接口的概念還
不一樣,接口代表一組接口函數(shù)的集合體。
三、封裝分為兩個(gè)層面
封裝其實(shí)分為兩個(gè)層面,但無(wú)論哪種層面的封裝,都要對(duì)外界提供好訪(fǎng)問(wèn)你內(nèi)部隱藏內(nèi)容的接口(接口可以理解為入
口,有了這個(gè)入口,使用者無(wú)需且不能夠直接訪(fǎng)問(wèn)到內(nèi)部隱藏的細(xì)節(jié),只能走接口,并且我們可以在接口的實(shí)現(xiàn)上附加更
多的處理邏輯,從而嚴(yán)格控制使用者的訪(fǎng)問(wèn))
第一個(gè)層面的封裝(什么都不用做):創(chuàng)建類(lèi)和對(duì)象會(huì)分別創(chuàng)建二者的名稱(chēng)空間,我們只能用類(lèi)名.或者obj.的方式去
訪(fǎng)問(wèn)里面的名字,這本身就是一種封裝。
print(m1.brand) #實(shí)例化對(duì)象(m1.)
print(motor_vehicle.tag) #類(lèi)名(motor_vehicle.)
-------------輸出結(jié)果--------------
春風(fēng)
fuel oil
注意:對(duì)于這一層面的封裝(隱藏),類(lèi)名.和實(shí)例名.就是訪(fǎng)問(wèn)隱藏屬性的接口
第二個(gè)層面的封裝:類(lèi)中把某些屬性和方法隱藏起來(lái)(或者說(shuō)定義成私有的),只在類(lèi)的內(nèi)部使用、外部無(wú)法訪(fǎng)問(wèn),或
者留下少量接口(函數(shù))供外部訪(fǎng)問(wèn)。
Python中私有化的方法也比較簡(jiǎn)單,即在準(zhǔn)備私有化的屬性(包括方法、數(shù)據(jù))名字前面加兩個(gè)下劃線(xiàn)即可。
類(lèi)中所有雙下劃線(xiàn)開(kāi)頭的名稱(chēng)如__x都會(huì)自動(dòng)變形成:_類(lèi)名__x的形式:
class A:
__N=0 #類(lèi)的數(shù)據(jù)屬性就應(yīng)該是共享的,但是語(yǔ)法上是可以把類(lèi)的數(shù)據(jù)屬性設(shè)置成私有的如__N,會(huì)變形為_(kāi)A__N
def __init__(self):
self.__X=10 #變形為self._A__X
def __foo(self): #變形為_(kāi)A__foo
print('from A')
def bar(self):
self.__foo() #只有在類(lèi)內(nèi)部才可以通過(guò)__foo的形式訪(fǎng)問(wèn)到.
這種自動(dòng)變形的特點(diǎn):
1、類(lèi)中定義的__x只能在內(nèi)部使用,如self.__x,引用的就是變形的結(jié)果。
2、這種變形其實(shí)正是針對(duì)外部的變形,在外部是無(wú)法通過(guò)__x這個(gè)名字訪(fǎng)問(wèn)到的。
3、在子類(lèi)定義的__x不會(huì)覆蓋在父類(lèi)定義的__x,因?yàn)樽宇?lèi)中變形成了:_子類(lèi)名__x,而父類(lèi)中變形成了:_父
類(lèi)名__x,即雙下滑線(xiàn)開(kāi)頭的屬性在繼承給子類(lèi)時(shí),子類(lèi)是無(wú)法覆蓋的。
注意:對(duì)于這一層面的封裝(隱藏),我們需要在類(lèi)中定義一個(gè)函數(shù)(接口函數(shù))在它內(nèi)部訪(fǎng)問(wèn)被隱藏的屬性,然后
外部就可以使用了
這種變形需要注意的問(wèn)題是:
1、這種機(jī)制也并沒(méi)有真正意義上限制我們從外部直接訪(fǎng)問(wèn)屬性,知道了類(lèi)名和屬性名就可以拼出名字:_類(lèi)名__屬
性,然后就可以訪(fǎng)問(wèn)了,如a._A__N
a = A()
print(a._A__N)
print(a._A__X)
print(A._A__N)
--------輸出結(jié)果--------
0
10
0
2、變形的過(guò)程只在類(lèi)的定義是發(fā)生一次,在定義后的賦值操作,不會(huì)變形
a = A() #實(shí)例化對(duì)象a
print(a.__dict__) #打印變形的內(nèi)容
a.__Y = 20 #新增Y的值,此時(shí)加__不會(huì)變形
print(a.__dict__) #打印變形的內(nèi)容
---------輸出結(jié)果----------
{'_A__X': 10}
{'_A__X': 10, '__Y': 20} #發(fā)現(xiàn)后面的Y并沒(méi)有變形
3、在繼承中,父類(lèi)如果不想讓子類(lèi)覆蓋自己的方法,可以將方法定義為私有的
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()
--------輸出結(jié)果----------
from B
看一下把fa被定義成私有的情況:
class A: #把fa定義成私有的,即__fa
def __fa(self): #在定義時(shí)就變形為_(kāi)A__fa
print("from A")
def test(self):
self.__fa() #只會(huì)與自己所在的類(lèi)為準(zhǔn),即調(diào)用_A__fa
class B(A):
def __fa(self): #b調(diào)用的是test,跟這個(gè)沒(méi)關(guān)系
print("from B")
b = B()
b.test()
-------輸出結(jié)果---------
from A
四、特性(property)
1、什么是特性property
property是一種特殊的屬性,訪(fǎng)問(wèn)它時(shí)會(huì)執(zhí)行一段功能(函數(shù))然后返回值(就是一個(gè)裝飾器)
注意:被property裝飾的屬性會(huì)優(yōu)先于對(duì)象的屬性被使用,而被propery裝飾的屬性,分成三種:property、被裝飾
的函數(shù)名.setter、被裝飾的函數(shù)名.deleter(都是以裝飾器的形式)。
class room: #定義一個(gè)房間的類(lèi)
def __init__(self,length,width,high):
self.length = length #房間的長(zhǎng)
self.width = width #房間的寬
self.high = high #房間的高
@property
def area(self): #求房間的平方的功能
return self.length * self.width #房間的面積就是:長(zhǎng)x寬
@property
def perimeter(self): #求房間的周長(zhǎng)的功能
return 2 * (self.length + self.width) #公式為:(長(zhǎng) + 寬)x 2
@property
def volume(self): #求房間的體積的功能
return self.length * self.width * self.high #公式為:長(zhǎng) x 寬 x 高
r1 = room(2,3,4) #實(shí)例化一個(gè)對(duì)象r1
print("r1.area:",r1.area) #可以像訪(fǎng)問(wèn)數(shù)據(jù)屬性一樣去訪(fǎng)問(wèn)area,會(huì)觸發(fā)一個(gè)函數(shù)的執(zhí)行,動(dòng)態(tài)計(jì)算出一個(gè)值
print("r1.perimeter:",r1.perimeter) #同上,就不用像調(diào)用綁定方法一樣,還得加括號(hào),才能運(yùn)行
print("r1.volume:",r1.volume) #同上,就像是把運(yùn)算過(guò)程封裝到一個(gè)函數(shù)內(nèi)部,我們不管過(guò)程,只要有結(jié)果就行
------------輸出結(jié)果---------------
r1.area: 6
r1.perimeter: 10
r1.volume: 24
注意:此時(shí)的特性arear、perimeter和volume不能被賦值。
r1.area = 8 #為特性area賦值
r1.perimeter = 14 #為特性perimeter賦值
r1.volume = 24 #為特性volume賦值
'''
拋出異常:
r1.area = 8 #第一個(gè)就拋異常了,后面的也一樣
AttributeError: can't set attribute
'''
2、為什么要用property
將一個(gè)類(lèi)的函數(shù)定義成特性以后,對(duì)象再去使用的時(shí)候obj.name,根本無(wú)法察覺(jué)自己的name是執(zhí)行了一個(gè)函數(shù)然后
計(jì)算出來(lái)的,這種特性的使用方式遵循了統(tǒng)一訪(fǎng)問(wèn)的原則。
class people: #定義一個(gè)人的類(lèi)
def __init__(self,name,sex):
self.name = name
self.sex = sex #p1.sex = "male",遇到property,優(yōu)先用property
@property #查看sex的值
def sex(self):
return self.__sex #返回正真存值的地方
@sex.setter #修改sex的值
def sex(self,value):
if not isinstance(value,str): #在設(shè)定值之前進(jìn)行類(lèi)型檢查
raise TypeError("性別必須是字符串類(lèi)型") #不是str類(lèi)型時(shí),主動(dòng)拋出異常
self.__sex = value #類(lèi)型正確的時(shí)候,直接修改__sex的值,這是值正真存放的地方
#這里sex前加"__",對(duì)sex變形,隱藏。
@sex.deleter #刪除sex
def sex(self):
del self.__sex
p1 = people("egon","male") #實(shí)例化對(duì)象p1
print(p1.sex) #查看p1的sex,此時(shí)要注意self.sex的優(yōu)先級(jí)
p1.sex = "female" #修改sex的值
print(p1.sex) #查看修改后p1的sex
print(p1.__dict__) #查看p1的名稱(chēng)空間,此時(shí)里面有sex
del p1.sex #刪除p1的sex
print(p1.__dict__) #查看p1的名稱(chēng)空間,此時(shí)發(fā)現(xiàn)里面已經(jīng)沒(méi)有sex了
-------------------輸出結(jié)果--------------------
male
female
{'name': 'egon', '_people__sex': 'female'}
{'name': 'egon'}
python并沒(méi)有在語(yǔ)法上把它們?nèi)齻€(gè)內(nèi)建到自己的class機(jī)制中,在C++里一般會(huì)將所有的所有的數(shù)據(jù)都設(shè)置為私有的
,然后提供set和get方法(接口)去設(shè)置和獲取,在python中通過(guò)property方法可以實(shí)現(xiàn)。
五、封裝與擴(kuò)展性
封裝在于明確區(qū)分內(nèi)外,使得類(lèi)實(shí)現(xiàn)者可以修改封裝內(nèi)的東西而不影響外部調(diào)用者的代碼;而外部使用用者只知道一
個(gè)接口(函數(shù)),只要接口(函數(shù))名、參數(shù)不變,使用者的代碼永遠(yuǎn)無(wú)需改變。這就提供一個(gè)良好的合作基礎(chǔ)——或者說(shuō)
,只要接口這個(gè)基礎(chǔ)約定不變,則代碼改變不足為慮。
#類(lèi)的設(shè)計(jì)者
class room: #定義一個(gè)房間的類(lèi)
def __init__(self,name,owner,length,width,high):
self.name = name
self.owner = owner
self.__length = length #房間的長(zhǎng)
self.__width = width #房間的寬
self.__high = high #房間的高
@property
def area(self): #求房間的平方的功能
return self.__length * self.__width #對(duì)外提供的接口,隱藏了內(nèi)部的實(shí)現(xiàn)細(xì)節(jié),\
# 此時(shí)我們想求的是房間的面積就是:長(zhǎng)x寬
實(shí)例化對(duì)象通過(guò)接口,調(diào)用相關(guān)屬性得到想要的值:
#類(lèi)的使用者
r1 = room("客廳","michael",20,30,9) #實(shí)例化一個(gè)對(duì)象r1
print(r1.area) #通過(guò)接口使用(area),使用者得到了客廳的面積
-------------輸出結(jié)果--------------
600 #得到了客廳的面積
擴(kuò)展原有的代碼,使功能增加:
#類(lèi)的設(shè)計(jì)者,輕松的擴(kuò)展了功能,而類(lèi)的使用者完全不需要改變自己的代碼
class room: #定義一個(gè)房間的類(lèi)
def __init__(self,name,owner,length,width,high):
self.name = name #房間名
self.owner = owner #房子的主人
self.__length = length #房間的長(zhǎng)
self.__width = width #房間的寬
self.__high = high #房間的高
@property
def area(self): #對(duì)外提供的接口,隱藏內(nèi)部實(shí)現(xiàn)
return self.__length * self.__width,\
self.__length * self.__width * self.__high #此時(shí)我們?cè)黾恿饲篌w積,
# 內(nèi)部邏輯變了,只需增加這行代碼就能簡(jiǎn)單實(shí)現(xiàn),而且外部調(diào)用感知不到,仍然使
# 用該方法,但是功能已經(jīng)增加了
對(duì)于類(lèi)的使用者,仍然在調(diào)用area接口的人來(lái)說(shuō),根本無(wú)需改動(dòng)自己的代碼,就可以用上新功能:
#類(lèi)的使用者
r1 = room("客廳","michael",20,30,9) #實(shí)例化一個(gè)對(duì)象r1
print(r1.area) #通過(guò)接口使用(area),使用者得到了客廳的面積
--------------輸出結(jié)果---------------
(600, 5400) #得到了新增的功能的值
總結(jié)
以上是生活随笔為你收集整理的python中的封装调用_Python基础之封装的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python中协程与函数的区别_pyth
- 下一篇: 100篇范文(7)