一 封裝
1 封裝介紹
封裝是面向對象三大特性最核心的一個特性
封裝指的就是把數據與功能都整合到一起,針對封裝到對象或者類中的屬性,可以嚴格控制對它們的訪問,分兩步實現:隱藏與開放接口
2、隱藏屬性
如何隱藏:
在屬性名前加前綴,就會實現一個對外隱藏屬性效果。Python 的 class 機制采用了雙下划線開頭的方式將屬性隱藏起來(設置成私有的),這其實僅僅只是一種變形的操作 ,類中所有雙下划線的屬性都會在類定義階段檢測語法時自動變成 _類名__屬性的形式:
該隱藏需要注意的問題:
1)在類外部無法直接訪問雙下划線開頭的屬性,但是知道了類名和屬性名就可以拼出名字:_類名 __ 屬性,然后就可以訪問了,如 _ Foo . __ A。所以說這種操作並沒有嚴格意義上地限制外部訪問,僅僅只是一種語法意義上的變形。
class Foo:
__x = 1 # _Foo__x
def __f1(self): #Foo_f1
print('from test')
print(Foo.__dict__)
#{'__module__': '__main__', '_Foo__x': 1, '_Foo__f1': <function Foo.__f1 at 0x10ab1c400>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
#print(Foo.__x) #報錯,訪問不到
print(Foo_Foo.__x) #1
print(Foo._Foo__f1) #<function Foo.__f1 at 0x10ab1c400>
2)這種隱藏是對外不對內,因為__ 開頭的屬性會在檢查類體代碼語法時統一發生變形
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)
# print(Foo.__x) #type object 'Foo' has no attribute '__x'
print(Foo.__f1) #'Foo' has no attribute '__f1'
obj=Foo()
obj.f2() #<function Foo.__f1 at 0x107057400>
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)
Foo.__y=3
print(Foo.__dict__)
#{'__module__': '__main__', '_Foo__x': 1, '_Foo__
# f1': <function Foo.__f1 at 0x10793b400>,
# 'f2': <function Foo.f2 at 0x10793b1e0>,
# '__dict__': <attribute '__dict__' of 'Foo' objects>,
# '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, '__y': 3}
print(Foo.__y) #3
class Foo:
__x = 1 # _Foo__x = 1
def __init__(self,name,age):
self.__name=name
self.__age=age
obj=Foo('egon',18)
print(obj.__dict__) #{'_Foo__name': 'egon', '_Foo__age': 18}
print(obj.name, obj.age) #'Foo' object has no attribute 'name'
3、開發接口
為何要隱藏屬性
1)隱藏數據屬性"將數據隱藏起來限制了類外部對數據的直接操作。
類內應該提供相應的接口來允許類外部間接地操作數據,接口之上可以附加額外的邏輯來對數據的操作進行嚴格地控制"
class People:
def __init__(self, name):
sel.__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')
#print(obj.name) #無法直接用名字屬性
obj.set_name('EGON')
obj.set_name(123123)
obj.get_name()
定義屬性就是為了使用,所以隱藏並不是目的
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
property 是一個裝飾器,用來綁定給對象的方法,將方法偽造成一個數據屬性
Ps:裝飾器是在不修改被裝飾對象源代碼以及調用方式的前提下為被裝飾對象添加新功能的可調用對象
案例一:
BMI指數是用來衡量一個人的體重與身高對健康影響的一個指標,計算公式為:
體質指數(BMI)=體重(kg)÷身高^2(m)
例:70kg÷(1.75×1.75)=22.86
代碼一:
class People:
def __init__(self, name, weight, height):
self.name = name
self.weight = weight
self.height = height
def bmi(self):
return self.weight/(self.height ** 2)
obj1 = People('han', 70, 1.83)
print(obj1.bmi())
將 bmi 定義為函數的原因
1、從 bmi的公式上課,bmi 應該是觸發功能計算得到的
2、bmi 是隨着身高、體重的變化而動態變換的,不是一個固定的值,它每次都是需要臨時計算得到的。
但是所以從使用者角度來看, bmi 聽起來更像是一個數據屬性,而非功能,所以從使用者角度出發,使用bmi 應該是調用數據屬性,而不是通過調用方法名( ) 方式拿到bmi 的數據,因此我們可以使用裝飾器---property,將對象的方法偽造成數據屬性。
初版
代碼二:使用 property 裝飾器
class People:
def __init__(self, name, weight, height):
self.name = name
self.weight = weight
self.height = height
#將方法偽造成數據屬性,可以通過對象.方法名運行拿到返回值
@property # bmi = property(bmi)
def bmi(self):
return self.weight/(self.height ** 2)
obj1 = People('han', 75, 1.85)
print(obj1.bmi) ##觸發方法bmi的執行,將obj自動傳給self,執行后返回值作為本次引用的結果
21.913805697589478
進階版
案例二:使用 property 有效地保證了屬性訪問的一致性。
如果有一個數據屬性比較重要,不想直接被使用者隨意通過修改等操作數據,所以要對外界隱藏起來,同時開設接口,在接口上增添操作數據條件規范。
class People:
def __init__(self, name):
self.__name = name #隱藏數據屬性
#開設接口
def get_name(self, name):
return self__name
def set_name(self, val):
if type(val) is not str:
print('必須傳入 str 類型')
return
self.__name = name
def del_name(self):
print('不可以刪除')
# del self.__name
#name:是想讓使用者使用的名字
name = property(get_name, set_name, del_name) #裝飾所有方法,同時返回給 name,當通過調用或者name時會自動觸發對應的方法
obj1 = People('han')
#人類正常的思維邏輯
print(obj1.name)#han
obj1.name = 18 #打印:必須傳入 str 類型
del obj1.name #打印:不可以刪除
終版
案例三:
使用 property 可以有效地保證了屬性,另外 property 還提供了設置和刪除屬性的功能
class People:
def __init_(self, name):
self.__name = name
@property # name= property(name) 裝飾 name
def name(self) #obj1.name
return self.__name
@name.setter #通過裝飾 name,給name 方法添加修改功能
def name(self, val): #obj1.name = 'HAN'
if type(val) is not str:
print('必須傳入 str 類型')
return
self.__name = val
@name.deleter #通過
def name(self): #del obj1.name
print('不讓刪除')
#del self.__name
obj1 = People('han')
#人類正常的思維邏輯
print(boj1.name) #觸發查看功能
obj1.name = 18 #觸發修改功能
del obj1.name #觸發刪除功能
property 其實也是一個類,通過裝飾被裝飾對象如(name), ==》name = procperty(name ) 得到一個帶有裝飾器功能的對象, @name.setter:其實是調用 帶有裝飾器功能的對象的綁定方法setter