【目錄】@2020.4.8
一、引入
二、隱藏屬性
1、如何隱藏屬性
2、需要注意的幾點問題
三、開放介面
1、隱藏資料介面
2、隱藏函式介面
四、裝飾器property
一、引入
面向物件編程有三大特性:封裝、繼承、多型,其中最重要的一個特性就是封裝,
封裝指的就是把資料與功能都整合到一起,之前所說的”整合“二字其實就是封裝的通俗說法,
除此之外,針對封裝到物件或者類中的屬性,我們還可以嚴格控制對它們的訪問,分兩步實作:隱藏與開放介面
二、隱藏屬性
1、為何要隱藏屬性
-
隱藏資料屬性,將資料隱藏起來就限制了類外部對資料的直接操作,
-
隱藏函式屬性,目的是隔離程式的復雜度 ,
2、如何隱藏屬性?
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(obbj.__x) # 報錯AttributeError:物件obj沒有屬性__x
3、需要注意的幾點問題
(1)在類外部無法直接訪問雙下滑線開頭的屬性,但知道了類名和屬性名就可以拼出名字: _類名__屬性,然后就可以訪問了,如foo._a__n所以說這種操作并沒有嚴格意義上地限制外部訪問,僅僅只是一種語法意義上的變形,
class Foo: __x = 1 # _Foo__x def __f1(self): # _Foo__f1 print('from test') print(Foo.__dict__) # 查看類Foo的屬性 print(Foo._Foo__x) # 訪問隱藏的資料屬性 __x print(Foo._Foo__f1) #訪問隱藏的函式屬性 __f1
(2) 這種隱藏對外不對內,即在類內部是可以直接訪問雙下滑線開頭的屬性的,比如self.__f1(),
因為在類定義階段,類內部雙下滑線開頭的屬性統一發生了變形,
class Foo: __x = 1 # _Foo__x = 1 def __f1(self): # _Foo__f1 print('from test') def f2(self): print(self.__x) # print(self._Foo__x) print(self.__f1) # print(self._Foo__f1) ,得到 _Foo__f1 的記憶體地址 print(self.__f1()) # print(self._Foo__f1()),列印函式 _Foo__f1的回傳結果,先是列印輸出 from test,再列印回傳值,若未賦值,默認為None # print(Foo.__x) # AttributeError: type object 'Foo' has no attribute '__x' # print(Foo.__f1) # AttributeError: type object 'Foo' has no attribute '__f1' obj=Foo() obj.f2() # 輸出結果: 1 <bound method Foo.__f1 of <__main__.Foo object at 0x000001B109AF9760>> from test None
#為何回傳一個 None 值?
print(self.__f1()) # print(self._Foo__f1()),列印函式 _Foo__f1的回傳結果,先是列印輸出 from test,再列印回傳值,若未賦值,默認為None
#為何回傳一個 None 值?
1 class Foo: 2 __x = 1 # _Foo__x = 1 3 4 def __f1(self): # _Foo__f1 5 print('from test') 6 return 2 # 若沒有設回傳值,則默認回傳 None 7 # return 'hello world' #也可回傳字串,可以回傳任意格式正確的值 8 # return {1,2,3,4} 9 10 11 def f2(self): 12 print(self.__x) # print(self._Foo__x) 13 print(self.__f1) # print(self._Foo__f1) 14 print(self.__f1()) 15 16 # print(Foo.__x) # AttributeError: type object 'Foo' has no attribute '__x' 17 # print(Foo.__f1) # AttributeError: type object 'Foo' has no attribute '__f1' 18 obj=Foo() 19 obj.f2() 20 21 22 # 輸出結果: 23 # 1 24 # <bound method Foo.__f1 of <__main__.Foo object at 0x000001801DAB9760>> 25 # from test 26 # 2 27 28 29 # ------將 f1改為正常的函式,不隱藏,直接執行 30 31 class Foo: 32 __x = 1 # _Foo__x = 1 33 34 def f1(self): # _Foo__f1 35 print('from test') 36 37 obj=Foo() 38 obj.f1() # 直接執行 f1() 39 40 # 輸出結果: 41 from test 42 43 44 # -------不管f1隱藏與否, print(self.f1()) # print(self._Foo__f1()) ,都是在列印 f1方法的回傳結果,一定要有個回傳值,即return 回傳值 45 46 class Foo: 47 __x = 1 # _Foo__x = 1 48 49 def f1(self): # _Foo__f1 50 print('from test') 51 return 1 # 若沒有設回傳值,則默認回傳 None 52 53 def f2(self): 54 print(self.__x) # print(self._Foo__x) 55 print(self.f1) # print(self._Foo__f1) 56 print(self.f1()) # print(self._Foo__f1()) 列印 f1方法的回傳結果,先是執行 print('from test'),再列印回傳值,若沒有設回傳值,則默認回傳 None 57 58 obj=Foo() 59 obj.f2() 60 61 # 輸出結果: 62 1 63 <bound method Foo.f1 of <__main__.Foo object at 0x000001CA79079760>> 64 from test 65 1折騰了一下子---View Code
(3)變形操作只在類定義階段發生一次,在類定義完之后的賦值操作,即之后定義的__開頭的屬性,都不會變形,
class Foo: __x = 1 # _Foo__x = 1 def __f1(self): # _Foo__f1 print('from test') def f2(self): print(self.__x) # print(self._Foo__x) print(self.__f1) # print(self._Foo__f1) obj=Foo() obj.f2() Foo.__y=3 # 在定義完類Foo之后,定義的__y,不會變形,不屬于被隱藏的屬性 print(Foo.__dict__) print(Foo.__y) 輸出結果: 1 # print(self.__x) <bound method Foo.__f1 of <__main__.Foo object at 0x000001FB91269760>> # print(self.__f1),回傳 __f1的記憶體地址 {'__module__': '__main__',
'_Foo__x': 1,
'_Foo__f1': <function Foo.__f1 at 0x000001FB912DA1F0>,
'f2': <function Foo.f2 at 0x000001FB912DA310>,
'__dict__': <attribute '__dict__' of 'Foo' objects>,
'__weakref__': <attribute '__weakref__' of 'Foo' objects>,
'__doc__': None,
'__y': 3} # print(Foo.__dict__),可以看到,'__y': 3,即在定義完類Foo之后,定義的__y,沒有變形,不屬于被隱藏的屬性 3 # print(Foo.__y)
三、開放介面
定義屬性,就是為了使用,所以隱藏并不是目的
1、隱藏資料介面
(1)隱藏資料屬性的一個栗子
class Foo: __x = 1 # _Foo__x = 1 def __init__(self,name,age): self.__name=name self.__age=age # obj=Foo() # 傳參必須滿足函式定義設定的引數個數,否則報錯 TypeError: __init__() missing 2 required positional arguments: 'name' and 'age' obj=Foo('egon',18) print(obj.__dict__) # 輸出結果:{'_Foo__name': 'egon', '_Foo__age': 18} # print(obj.name,obj.age) # __name 和 __age 屬性均為隱藏的資料屬性,是無法直接訪問的,
輸出結果報錯: AttributeError: 'Foo' object has no attribute 'name'
(2)為何要隱藏資料屬性,并開放介面
隱藏資料屬性,將資料隱藏起來就限制了類外部對資料的直接操作,
然后類內應該提供相應的介面來允許類外部間接地操作資料,介面之上可以附加額外的邏輯來對資料的操作進行嚴格地控制:
# 設計者:egon class People: def __init__(self, name): self.__name = name # __name 為隱藏的資料屬性 def get_name(self): # 通過該介面就可以間接地訪問到名字屬性 print('小垃圾,不讓看') print(self.__name) def set_name(self,val): if type(val) is not str: print('小垃圾,必須傳字串型別') return self.__name=val # 使用者:王鵬 obj = People('egon') # People('egon'),self.__name = name最先初始化name,將值egon的記憶體地址傳給__name # print(obj.name) # 無法直接用名字屬性 AttributeError: 'People' object has no attribute 'name' obj.set_name('EGON') # 呼叫函式set_name,將初始化的值 egon 改為了 EGON,將 即此時__name保存的記憶體地址指向 EGON obj.get_name() #再呼叫函式get_name, print(self.__name),列印得到的值就是 EGON # 輸出結果: # 小垃圾,不讓看 # EGON obj.set_name(123123123) # 修改名字失敗,回傳原值 obj.get_name() # 輸出結果: # 小垃圾,必須傳字串型別 # 小垃圾,不讓看 # egon
沒事瞎折騰版本:
# ---瞎折騰版本 1 ----調換get set 函式呼叫的順序------ # 設計者:egon class People: def __init__(self, name): self.__name = name # __name 為隱藏的資料屬性 def get_name(self): # 通過該介面就可以間接地訪問到名字屬性 print('小垃圾,不讓看') print(self.__name) def set_name(self,val): if type(val) is not str: print('小垃圾,必須傳字串型別') return self.__name=val # 使用者:王鵬 obj = People('egon') # People('egon'),self.__name = name最先初始化name,將值egon的記憶體地址傳給__name # obj.get_name() # self.__name = name,先呼叫函式get_name,拿到了初始化的值 egon的地址,print(self.__name),此時__name保存的記憶體地址仍然指向值 egon # obj.set_name('EGON') # 呼叫 set_name函式,self.__name=val,val指向的記憶體地址是 EGON,并將其地址傳給 __name, # 此時__name保存的記憶體地址指向值 EGON,但是后面沒有print命令來輸出結果EGON # 輸出結果: # 小垃圾,不讓看 # egon # obj.get_name() # obj.set_name(1314131424) # 輸出結果: # 小垃圾,不讓看 # egon # 小垃圾,必須傳字串型別 # ---瞎折騰版本2 ----再增加了set_name()的列印輸出功能------ # 設計者:egon class People: def __init__(self, name): self.__name = name # __name 為隱藏的資料屬性 def get_name(self): # 通過該介面就可以間接地訪問到名字屬性 print('小垃圾,不讓看') print(self.__name) def set_name(self,val): if type(val) is not str: print('小垃圾,必須傳字串型別') return self.__name=val print(self.__name) # 使用者:王鵬 obj = People('egon') # People('egon'),self.__name = name最先初始化name,將值egon的記憶體地址傳給__name obj.get_name() # self.__name = name,先呼叫函式get_name,拿到了初始化的值 egon的地址,print(self.__name),此時__name保存的記憶體地址仍然指向值 egon obj.set_name('EGON') # 呼叫 set_name函式,self.__name=val,val指向的記憶體地址是 EGON,并將其地址傳給 __name, # 輸出結果: # 小垃圾,不讓看 # egon # EGON obj.get_name() obj.set_name(1314131424) # 輸出結果: # 小垃圾,不讓看 # egon # 小垃圾,必須傳字符串型別兩個瞎折騰的栗子--View Code
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()
3、總結—隱藏屬性與開放介面
隱藏屬性與開放介面,本質就是為了明確地區分內外,類內部可以修改封裝內的東西,而不影響外部呼叫者的代碼;
而類外部只需拿到一個介面,只要介面名、引數不變,則無論設計者如何改變內部實作代碼,使用者均無需改變代碼,
這就提供一個良好的合作基礎,只要介面這個基礎約定不變,則代碼的修改不足為慮,
四、裝飾器property
# 裝飾器是在不修改被裝飾物件源代碼以及呼叫方式的前提下為被裝飾物件添加新功能的可呼叫物件,
Python專門提供了一個裝飾器property,可以將類中的函式“偽裝成”物件的資料屬性,
物件在訪問該特殊屬性時會觸發功能的執行,然后將回傳值作為本次訪問的結果,
栗子如下:
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
定義函式的原因
# 1、從bmi的公式上看,bmi應該是觸發功能計算得到的
# 2、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) >>> obj=People('lili',75,1.85) >>> obj.bmi #觸發方法bmi的執行,將obj自動傳給self,執行后回傳值作為本次參考的結果 21.913805697589478
使用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 be str' %value) ... self.__NAME=value #通過型別檢查后,將值value存放到真實的位置self.__NAME ... @name.deleter ... def name(self): ... raise PermissionError('Can not delete') >>> f=Foo('lili') >>> f.name lili >>> f.name='LiLi' #觸發name.setter裝飾器對應的函式name(f,’Egon') >>> f.name=123 #觸發name.setter對應的的函式name(f,123),拋出例外TypeError >>> del f.name #觸發name.deleter對應的函式name(f),拋出例外PermissionError
另一種使用方法--——name=property(get_name,set_name,del_name)
# 案例二: class People: def __init__(self, name): self.__name = name def get_name(self): return self.__name def set_name(self, val): if type(val) is not str: print('必須傳入str型別') return self.__name = val def del_name(self): print('不讓洗掉') # del self.__name name=property(get_name,set_name,del_name) obj1=People('egon') # print(obj1.get_name()) # obj1.set_name('EGON') # print(obj1.get_name()) # obj1.del_name() # 人正常的思維邏輯 print(obj1.name) # # obj1.name=18 # del obj1.name
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/161028.html
標籤:Python
上一篇:購物車程式-練習
