封裝


封裝

24、封裝

一. 引入

面向對象編程有三大特征:封裝、繼承、多態,其中最重要的一個特征就是封裝。封裝指的就是把數據與功能都整合到一起。除此之外,針對封裝到對象或者類中的屬性,我們還可以嚴格控制對它們的訪問,分兩步實現:隱藏與開放接口

二. 隱藏屬性

Python的Class機制采用雙下划線開頭的方式將屬性隱藏起來(設置成私有的),但其實這僅僅只是一種變形操作,類中所有下划線開頭的屬性都會在類定義階段、檢測語法時自動變成“_類名__屬性名”的形式:

class Foo:
   __N=0 #_Foo__N
   
   def __init__(self):   #定義函數時,會檢測函數語法,所以__開頭的屬性也會變形
       self.__x=10   #變形為self.Foo__x
       
   def __f1(self):    #變形為Foo__f1
       print('__f1 run)
   
   def f2(self):    #定義函數時,會檢測函數語法,所以__開頭的屬性也會變形
       self.__f1()    #變形為self.__Foo__f1()
             
print(Foo.__N)    #報錯AttributeError:類Foo沒有屬性__N

obj=Foo()
print(obj.__x)    #報錯AttributeError:對象obj沒有屬性__x

這種變形需要注意的問題是:

1、在類外部無法直接訪問雙下划線開頭的屬性,但知道了類名和屬性名就可以拼出名字:_類名__屬性,然后就可以訪問了,如Foo._A__N,所以說這種操作並沒有嚴格意義上地限制外部訪問,僅僅只是一種語法意義上的變形
print(Foo.__dict__)
#>>> mappingproxy({..., '_Foo__N': 0, ...})

print(obj.__dict__)
#>>> {'_Foo__x': 10}

print(Foo._Foo__N)
#>>> 0
print(obj._Foo__x)
#>>> 10
print(obj._Foo__N)
#>>> 0
2、在類內部是可以直接訪問雙下划線開頭的屬性的,比如self.__f1(),因為在類定義階段類內部雙下划線開頭的屬性統一發生了變形
print(obj.f2())
#>>> __f1 run
3、變形操作只在類定義階段發生一次,在類定義之后的賦值操作,不會變形。
Foo.__M = 100
print(Foo.__M)
#>>> 100

print(Foo.__dict__)
#>>> mappingproxy({..., '__M': 100,...})

obj.__y = 20
print(obj.__dict__)
#>>> {'_Foo__x': 10, '__y': 20}

print(obj.__y)
#>>> 20

三. 開放接口

3.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('lili',18)
t.set_info('lili',19)   #名字為字符串類型,年齡為整形,可以正常設置
print(t.tell_info())

#>>> 姓名:LiLi,年齡:19

3.2 隱藏函數屬性

目的的是為了隔離復雜度,例如ATM程序的取款功能,該功能有很多其他功能組成,比如插卡、身份認證、輸入金額、打印小票、取錢等,而對使用者來說,只需要開發取款這個功能接口即可,其余功能我們都可以隱藏起來

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()

obj = ATM()
obj.withdraw()

#>>>插卡
# 用戶認證
# 輸入取款金額
# 打印賬單
# 取款

總結隱藏屬性與開放接口,本質就是為了明確區分內外,類內部可以修改封裝內的東西而不影響外部調用者的代碼;而類外部只需要拿到一個接口,只要接口名、參數不變,則無論設計者如何改變內部實現代碼,使用者均無需改變代碼。這就提供一個良好的合作基礎,只要接口這個基礎約定不變,則代碼的修改不足為慮。

四. property

Python專門提供了一個裝飾器property,可以將類中的函數“偽裝成”對象的數據屬性,對象在訪問該特殊屬性時會觸發功能的執行,然后將返回值作為本次訪問的結果,例如

class People:
def __init__(self,name,weight,height):
self.name = name
self.weight = weight
self.height = height
@property
def bmi(self):
bmi = self.weight/(self.height**2)
print(bmi)

obj = People('momo',75,1.80)
obj.bmi #觸發方法bmi的執行,將obj自動傳給self,執行后返回值作為本次引用的結果

#>>> 23.148148148148145

使用property有效地保證了屬性訪問的一致性。另外property還提供設置和刪除屬性的功能,如下

class Foo:
def __init__(self,val):
self.__NAME = val
@property
def name(self):
return self.__NAME
@name.setter
def name(self,value):
if not isinstance(value,str):
raise TypeError('%s must to str' %value)
self.__NAME = value
@name.deleter
def name(self):
raise PermissionError('Can not delete')

f = Foo('lili')
print(f.name)
#lili

f.name = 'LiLi'
print(f.name)
#LiLi

f.name = 123
print(f.name)
#觸發name.setter對應的的函數name(f,123),拋出異常TypeError

del f.name
#觸發name.deleter對應的函數name(f),拋出異常PermissionError

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM