您好,登錄后才能下訂單哦!
[TOC]
? 面向對象編程有三大特性:封裝、繼承、多態,其中最重要的一個特性就是封裝。封裝指的就是把數據與功能都整合到一起,聽起來是不是很熟悉,沒錯,我們之前所說的”整合“二字其實就是封裝的通俗說法。除此之外,針對封裝到對象或者類中的屬性,我們還可以嚴格控制對它們的訪問,分兩步實現:隱藏與開放接口
插圖:惡搞圖16
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
插圖:惡搞圖17
這種變形需要注意的問題是:
1、在類外部無法直接訪問雙下滑線開頭的屬性,但知道了類名和屬性名就可以拼出名字:_類名_屬性,然后就可以訪問了,如Foo._A\_N,所以說這種操作并沒有嚴格意義上地限制外部訪問,僅僅只是一種語法意義上的變形。
>>> Foo.__dict__
mappingproxy({..., '_Foo__N': 0, ...})
>>> obj.__dict__
{'_Foo__x': 10}
>>> Foo._Foo__N
0
>>> obj._Foo__x
10
>>> obj._Foo__N
0
2、在類內部是可以直接訪問雙下滑線開頭的屬性的,比如self.__f1(),因為在類定義階段類內部雙下滑線開頭的屬性統一發生了變形。
>>> obj.f2()
__f1 run
3、變形操作只在類定義階段發生一次,在類定義之后的賦值操作,不會變形。
>>> Foo.__M=100
>>> Foo.__dict__
mappingproxy({..., '__M': 100,...})
>>> Foo.__M
100
>>> obj.__y=20
>>> obj.__dict__
{'__y': 20, '_Foo__x': 10}
>>> obj.__y
20
插圖:惡搞圖18
定義屬性就是為了使用,所以隱藏并不是目的
將數據隱藏起來就限制了類外部對數據的直接操作,然后類內應該提供相應的接口來允許類外部間接地操作數據,接口之上可以附加額外的邏輯來對數據的操作進行嚴格地控制
>>> 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') # 年齡不為整型,拋出異常
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 11, in set_info
TypeError: 年齡必須是整型
>>> t.set_info('LiLi',19) # 名字為字符串類型,年齡為整形,可以正常設置
>>> t.tell_info() # 查看老師的信息
姓名:LiLi,年齡:19
插圖:惡搞圖19
目的的是為了隔離復雜度,例如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()
插圖:惡搞圖20
總結隱藏屬性與開放接口,本質就是為了明確地區分內外,類內部可以修改封裝內的東西而不影響外部調用者的代碼;而類外部只需拿到一個接口,只要接口名、參數不變,則無論設計者如何改變內部實現代碼,使用者均無需改變代碼。這就提供一個良好的合作基礎,只要接口這個基礎約定不變,則代碼的修改不足為慮。
BMI指數是用來衡量一個人的體重與身高對健康影響的一個指標,計算公式為
體質指數(BMI)=體重(kg)÷身高^2(m)
EX:70kg÷(1.75×1.75)=22.86
身高或體重是不斷變化的,因而每次想查看BMI值都需要通過計算才能得到,但很明顯BMI聽起來更像是一個特征而非功能,為此Python專門提供了一個裝飾器property,可以將類中的函數“偽裝成”對象的數據屬性,對象在訪問該特殊屬性時會觸發功能的執行,然后將返回值作為本次訪問的結果,例如
>>> 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
插圖:惡搞圖21
使用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
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。