封裝
0 引入
面向?qū)ο缶幊逃腥筇匦裕悍庋b、繼承、多態(tài),其中最重要的一個(gè)特性就是封裝。封裝指的就是把數(shù)據(jù)與功能都整合到一起,聽起來是不是很熟悉,沒錯(cuò),我們之前所說的”整合“二字其實(shí)就是封裝的通俗說法。除此之外,針對(duì)封裝到對(duì)象或者類中的屬性,我們還可以嚴(yán)格控制對(duì)它們的訪問,分兩步實(shí)現(xiàn):隱藏與開放接口
一、隱藏類屬性和方法
如何隱藏
- 在變量前加__
# 如何隱藏呢? 類屬性和 方法前面加__。對(duì)外不對(duì)內(nèi)
class Foo:
__x =1
def __f1(self):
print("__f1內(nèi)部可以訪問")
def f2(self):
self.__f1()
print(self.__x)
f = Foo()
#f.x 不能訪問__x
print(Foo.__dict__)
"""{'__module__': '__main__', '_Foo__x': 1,
'_Foo__f1': <function Foo.__f1 at 0x0000000002784310>,
'__dict__': <attribute '__dict__' of 'Foo' objects>,
'__weakref__': <attribute '__weakref__' of 'Foo' objects>,
'__doc__': None}
"""
# 從上面可以看出外部訪問可以通過_Foo__x
print(Foo._Foo__x) # 1
print(Foo._Foo__f1) # <function Foo.__f1 at 0x0000000002784310>
# __對(duì)外不對(duì)內(nèi),內(nèi)部可以直接訪問__,如下:
Foo().f2()
"""
__f1內(nèi)部可以訪問
1
"""
隱藏屬性
設(shè)計(jì)者: 阿登
class People:
def __init__(self, name):
self.__name = name
def get_name(self):
# 通過接口就可以間接地訪問到名字屬性
print(self.__name)
def change_name(self, name):
if not isinstance(name, str):
print("必須傳字符串")
raise TypeError("必須傳字符串")
self.__name = name
print(f"修改后名字為:{self.__name}")
使用者:向佳
p = People("咸維維")
那么不能直接訪問__name,我可以開放一個(gè)接口訪問
p.get_name() # 這樣就能訪問到name了
再比如我想要修改__name的屬性,作為設(shè)計(jì)者我給你開放一個(gè)接口讓你修改
作為設(shè)計(jì)者_(dá)_name的設(shè)計(jì)我并不想讓使用者直接修改 刪除等操作。那么
作為設(shè)計(jì)者我給你開放一個(gè)接口供你訪問和修改屬性。
p.change_name("阿登")
隱藏函數(shù)屬性
目的的是為了隔離復(fù)雜度,當(dāng)使用調(diào)用的時(shí)候點(diǎn)方法的時(shí)候只需要使用想調(diào)的方法即可,無需在多個(gè)方法中選擇,例如ATM程序的取款功能,該功能有很多其他功能組成,比如插卡、身份認(rèn)證、輸入金額、打印小票、取錢等,而對(duì)使用者來說,只需要開發(fā)取款這個(gè)功能接口即可,其余功能我們都可以隱藏起來
>>> class ATM:
... def __card(self): #插卡
... print('插卡')
... def __auth(self): #身份認(rèn)證
... print('用戶認(rèn)證')
... 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()
...
>>> obj=ATM()
>>> obj.withdraw()
為什么要隱藏
總結(jié)隱藏屬性與開放接口,本質(zhì)就是為了明確地區(qū)分內(nèi)外,類內(nèi)部可以修改封裝內(nèi)的東西而不影響外部調(diào)用者的代碼;而類外部只需拿到一個(gè)接口,只要接口名、參數(shù)不變,則無論設(shè)計(jì)者如何改變內(nèi)部實(shí)現(xiàn)代碼,使用者均無需改變代碼。這就提供一個(gè)良好的合作基礎(chǔ),只要接口這個(gè)基礎(chǔ)約定不變,則代碼的修改不足為慮。
property裝飾器
裝飾器是在不修改被裝飾器對(duì)象源碼代碼以及調(diào)用方式的前提下為被裝飾對(duì)象添加新功能的可調(diào)用對(duì)象
例一:BMI指數(shù)(bmi是計(jì)算而來的,但很明顯它聽起來像是一個(gè)屬性而非方法,如果我們將其做成一個(gè)屬性,更便于理解)
成人的BMI數(shù)值:
過輕:低于18.5
正常:18.5-23.9
過重:24-27
肥胖:28-32
非常肥胖, 高于32
體質(zhì)指數(shù)(BMI)=體重(kg)÷身高^2(m)
EX:70kg÷(1.75×1.75)=22.86
身高或體重是不斷變化的,因而每次想查看BMI值都需要通過計(jì)算才能得到,但很明顯BMI聽起來更像是一個(gè)特征而非功能,
為此Python專門提供了一個(gè)裝飾器property,可以將類中的函數(shù)“偽裝成”對(duì)象的數(shù)據(jù)屬性,
對(duì)象在訪問該特殊屬性時(shí)會(huì)觸發(fā)功能的執(zhí)行,然后將返回值作為本次訪問的結(jié)果,例如
property是一個(gè)裝飾器,是用來綁定給對(duì)象的方法偽裝成一個(gè)數(shù)據(jù)屬性,就可以不用加括號(hào)調(diào)用
class People:
def __init__(self,name,weight,height):
self.name=name
self.weight=weight
self.height=height
# bmi聽起來更像是1個(gè)數(shù)據(jù)屬性,而非功能
@property
def bmi(self):
return self.weight / (self.height**2)
p1=People('adneg',75,1.80)
print(p1.bmi) # 23.148148148148145
p1.weight = 70
print(p1.bmi) # 21.604938271604937
使用property有效地保證了屬性訪問的一致性。另外property還提供設(shè)置和刪除屬性的功能,如下
class People1:
def __init__(self, name):
self.__name = name
def get_name(self):
# 通過接口就可以間接地訪問到名字屬性
return self.__name
def change_name(self, name):
if not isinstance(name, str):
print("必須傳字符串")
raise TypeError("必須傳字符串")
self.__name = name
print(f"修改后名字為:{self.__name}")
def del_name(self):
print("不讓刪")
# del self.__name
name = property(get_name, change_name, del_name)
obj1 = People1("阿登")
"""
1. 屬性隱藏起來
2. 開放接口
3. name = property(get_name, change_name, del_name)
"""
# obj1.get_name()
# obj1.change_name("向佳")
# obj1.del_name()
print(obj1.name)
del obj1.name
obj1.name = "王潤(rùn)"
name = property(get_name, change_name, del_name)
上面那種方式是老版本的使用方式下面用另外一種更直觀的方式實(shí)現(xiàn):
如果有個(gè)關(guān)鍵屬性你想讓外部使用者的操作嚴(yán)格控制起來,步驟:
1. 隱藏起來
2. 開放三個(gè)接口 查看 修改 刪除 三個(gè)接口,三個(gè)接口的命名一直,同需要操作的名字一樣
3.三個(gè)接口操作偽裝成一個(gè)數(shù)據(jù)屬性 查找@property 修改 @name.setter 刪除 @name.deleter name就是你想操作的屬性name,如果編程name123那么你三個(gè)接口的方法名也需要編程name123 查找@property 修改 @name123.setter 刪除 @name123.deleter
class People2:
def __init__(self, name):
self.__name = name
@property # name = property(name)
def name(self): # p2.name
# 通過接口就可以間接地訪問到名字屬性
return self.__name
@name.setter
def name(self, name): # p2.name = "老胡"
if not isinstance(name, str):
print("必須傳字符串")
raise TypeError("必須傳字符串")
self.__name = name
print(f"修改后名字為:{self.__name}")
@name.deleter
def name(self): # del p2.name
print("不讓刪")
# del self.__name
p2 = People2("毛主席")
print(p2.name) # 毛主席
p2.name = "老胡" # 修改后名字為:老胡
del p2.name # 不讓刪