1. 物件導向的三大特性
物件導向程式設計有三大特性:封裝、繼承、多型
2. 理論
2.1 概念
封裝指的是把資料與功能都整合到一起,之前所說的”整合“二字其實就是封裝的通俗說法。
除此之外,針對封裝到物件或者類中的屬性,可以嚴格控制對它們的訪問,分兩步實現:隱藏與開放介面
2.2 封裝的目的
將某些屬性和方法隱藏起來,在程式外部看不到,其它程式無法呼叫
保護資料,可以防止外部程式碼隨意修改物件內部的資料
3. 如何進行封裝---隱藏屬性
3.1 隱藏屬性方法
Python的Class機制採用雙下劃線開頭的方式將屬性隱藏起來(設定成私有的)
但其實這僅僅只是一種變形操作,類中所有雙下滑線開頭的屬性都會在類定義階段、檢測語法時自動變成_類名__屬性名的形式
3.2 隱藏屬性訪問
在類外部無法直接訪問雙下滑線開頭的屬性,但知道了類名和屬性名就可以拼出名字:__類名__屬性,然後就可以訪問了,
如Person._Person__NAME,所以說這種操作並沒有嚴格意義上地限制外部訪問,僅僅只是一種語法意義上的變形
class Student:
_ _school = 'MIT' # 在需要被封裝資料屬性的位置,加_ _
def _ _init_ _(self, name, age, grade):
self.name = name
self.age = age
self.grade = grade
def show_info(self):
print(f'name is {self.name}, age is {self.age}, grade is {self.grade},'
f'school is {self.__school}')
def _ _lab(self): # 在需要被封裝函式屬性的位置,加__
print(f'{self.__school}的{self.name}正在做實驗')
print(Student._ _dict_ _) # '_Student_ _school': 'MIT' 類的屬性字典變成變成 _類名__屬性名的形式
stu1 = Student('neymar', 32, 2)
stu1.show_info() # name is neymar, age is 32, grade is 2,school is MIT
print(stu1._ _dict_ _) # {'name': 'neymar', 'age': 32, 'grade': 2}
stu1.school = 'California' # 隱藏school資料屬性後物件無法直接修改該屬性
print(stu1._ _dict_ _) # {'name': 'neymar', 'age': 32, 'grade': 2, 'school': 'California'}
print(Student._ _dict_ _) # '_Student__school': 'MIT'
# 透過 類名._類名__屬性名修改屬性
Student._Student_ _school = 'Harvard'
print(Student._ _dict_ _) # '_Student__school': 'Harvard'
print(stu1._Student_ _school) # Harvard
stu1._Student_ _lab() # Harvard的neymar正在做實驗
3.3 隱藏方法變形
在類內部是可以直接訪問雙下劃線開頭的屬性的,因為在類定義階段類內部雙下劃線開頭的屬性統一發生了變形。
class Student:
__school = 'MIT' # 本質上變形成了_Student__school
def __init__(self, name, age, grade):
self.__name = name # 本質上變形成了self._Student__name
self.age = age
self.grade = grade
def __lab(self): # 本質上變形成了_Student__lab
print(f'{self.__school}的{self.__name}正在做實驗')
def run(self):
self.__lab() # 本質上變形成了self._Student__lab())
print(self.__school) # 本質上變形成了self._Student__school
3.4 變形操作只會發生一次
變形操作只在類定義階段發生一次,在類定義之後的賦值操作,不會變形
class Student:
_ _school = 'MIT' # 本質上變形成了_Student_ _school
def _ _init_ _(self, name, age, grade):
self._ _name = name # 本質上變形成了self._Student_ _name
self.age = age
self.grade = grade
def _ _lab(self): # 本質上變形成了_Student__lab
print(f'{self._ _school}的{self._ _name}正在做實驗')
def run(self):
self._ _lab() # 本質上變形成了self._Student_ _lab())
print(self._ _school) # 本質上變形成了self._Student_ _school
Student._ _school = 'singapore'
print(Student._ _dict_ _) # '_Student_ _school': 'MIT' 資料屬性並沒有發生修改
3.5 總結
(1)在類內部 將變數名 用雙下劃線 宣告
在類初始化的時候就會由原來的變數名 __變數名 變換 變換成 _類名_ _變數名
(2)在類內部封裝起來的屬性
內部呼叫 self 去呼叫的時候直接 self.__變數名即可呼叫
如果是物件呼叫 隱藏起來的屬性 那就需要 物件._類名__變數名 去呼叫
(3)在類內部封裝屬性後變形只會發生一次
當使用物件或者類.__變數名去修改屬性的時候 修改的是 新的屬性而不是原來的屬性
4. 封裝之後如何訪問---開放介面
4.1 原理
將資料和功能隱藏起來就限制了類外部對資料和功能的直接操作,類內部應該提供相應的介面來允許類外部間接地運算元據和功能,介面之上可以增加額外的邏輯對資料和函式的操作進行嚴格限制
4.2 隱藏資料屬性
class Student:
def __init__(self, name, age, grade):
self.__name = name
self.__age = age
self.__grade = grade
def show_info(self):
print(f'{self.__name}的年齡是{self.__age}年級是{self.__grade}')
def set_info(self, name1, grade1):
if not name1.startswith('mit_'):
raise ValueError('名字必須以mit_開頭')
if not grade1.isdigit():
raise ValueError('年級必須是數字')
self.__name = name1 # 符合以上兩個條件才允許修改屬性
self.__grade = grade1
stu1 = Student(name='alan', age=30, grade=1)
stu1.show_info() # alan的年齡是30年級是1
# 物件無法直接修改類中資料屬性
stu1.name = 'messi'
stu1.show_info() # alan的年齡是30年級是1
# 想要修改類中資料屬性,必須透過介面進行修改
stu1.set_info(name1='messi', grade1='2') # ValueError: 名字必須以mit_開頭
stu1.set_info(name1='mit_messi', grade1=2)
# AttributeError: 'int' object has no attribute 'isdigit' int型別沒有isdigit方法
stu1.set_info(name1='mit_messi', grade1='2') # 修改成功
stu1.show_info() # mit_messi的年齡是30年級是2
4.3 隱藏函式屬性
# ATM程式的取款功能,由許多其它功能組成:插卡、輸入密碼、輸入取款金額、取款、列印回執等
# 而對於使用者來說,只需要withdraw這個功能介面即可,其餘功能都可以隱藏起來
class ATM:
def __inert_card(self):
print('插卡')
def __input_pwd(self):
print('輸入密碼')
def __input_money(self):
print('輸入取款金額')
def __take_money(self):
print('取款')
def __print_bill(self):
print('列印回執')
def withdraw(self):
self.__inert_card()
self.__input_pwd()
self.__input_money()
self.__take_money()
self.__print_bill()
obj = ATM()
obj.withdraw()
5. property屬性
5.1 引入
計算bmi
class Person:
def __init__(self, name, height, weight):
self.__name = name
self.__height = height
self.__weight = weight
def bmi(self):
info = f'{self.__name}的BMI數值是:{self.__weight / (self.__height ** 2)}'
return info
person1 = Person(name='haaland', height=1.95, weight=75)
print(person1.bmi()) # haaland的BMI數值是:19.723865877712033
# bmi計算得到的結果是一個數字,應該和姓名、身高、體重一樣是一個資料屬性,而不是函式屬性
# 給計算bmi的函式加一個property屬性,將函式屬性偽裝成資料屬性
class Person:
def __init__(self, name, height, weight):
self.__name = name
self.__height = height
self.__weight = weight
@property
def bmi(self):
info = f'{self.__name}的BMI數值是:{self.__weight / (self.__height ** 2)}'
return info
person1 = Person(name='haaland', height=1.95, weight=75)
# 函式屬性變成了資料屬性
print(person1.bmi) # haaland的BMI數值是:19.723865877712033
5.2 為什麼要用property
將一個類的函式定義成特性以後,物件再去使用的時候obj.name,根本無法察覺自己的name是執行了一個函式然後計算出來的,這種特性的使用方式遵循了統一訪問的原則
物件導向的封裝有三種方式:
public 這種就是不封裝,對外公開
protected 這種封裝方式對外不公開,但對子類公開
private 這種方式對任何都不公開
python並沒有在語法上將三種封裝方式內建到class機制中,透過property方法可以實現
5.3 setter與deleter裝飾器
setter與deleter裝飾器一般與property結合使用
class Student:
def __init__(self, name, age, grade):
self.__name = name
self.__age = age
self.__grade = grade
@property # 將函式屬性包裝成資料屬性
def name_tool(self):
return f'MIT_{self.__name}'
@name_tool.setter # 給以上包裝後的資料屬性設定值
def name_tool(self, value):
if not len(value) <= 3:
raise ValueError('名字必須是三個字及以下')
self.__name = value
@name_tool.deleter # 給以上包裝後的資料屬性刪除值
def name_tool(self):
del self.__name
stu1 = Student(name='lavigne', age=19, grade=2)
# 獲取完整名字 觸發property,此時name_tool函式中的self都是物件stu1本身
print(stu1.name_tool) # MIT_lavigne
print(stu1.__dict__) # {'_Student__name': 'lavigne', '_Student__age': 19, '_Student__grade': 2}
# 修改名字 觸發setter,所有self都是物件stu1本身
stu1.name_tool = 'lav'
print(stu1.__dict__) # {'_Student__name': 'lav', '_Student__age': 19, '_Student__grade': 2}
# 刪除名字 觸發deleter,所有self都是物件stu1本身
del stu1.name_tool
print(stu1.name_tool) # AttributeError: 'Student' object has no attribute '_Student__name'. Did you mean: '_Student__age'?
print(stu1.__dict__) # {'_Student__age': 19, '_Student__grade': 2}
5.4 使用property內建函式對外提供統一介面
class Student:
def __init__(self, name, age, grade):
self.__name = name
self.__age = age
self.__grade = grade
def __full_name(self):
return f'MIT_{self.__name}'
def __set_name(self, value):
if not len(value) <= 3:
raise ValueError('名字必須是三個字及以下')
print(self.__dict__)
self.__name = value
print(self.__dict__)
def __del_name(self):
print(self.__dict__)
del self.__name
print(self.__dict__)
# 不用裝飾器,使用property內建函式,對外提供統一介面
name_tool = property(fget=__full_name, fset=__set_name, fdel=__del_name)
stu1 = Student(name='lavigne', age=20, grade=3)
# 獲取完整名字 只需要呼叫介面即可,觸發property第一個引數函式
print(stu1.name_tool) # MIT_lavigne
# 修改名字 只需要呼叫介面即可,觸發property第二個引數函式
stu1.name_tool = 'lav'
# {'_Student__name': 'lavigne', '_Student__age': 20, '_Student__grade': 3}
# {'_Student__name': 'lav', '_Student__age': 20, '_Student__grade': 3}
# 刪除名字 只需要呼叫介面即可,觸發property第三個引數函式
del stu1.name_tool
# {'_Student__name': 'lav', '_Student__age': 20, '_Student__grade': 3}
# {'_Student__age': 20, '_Student__grade': 3}