面向對象封裝、繼承、多態
一、面向對象封裝
01. 封裝
-
封裝 是面向對象編程的一大特點
-
面向對象編程的 第一步 —— 將 屬性 和 方法 封裝 到一個抽象的 類 中
-
外界 使用 類 創建 對象,然後 讓對象調用方法
-
對象方法的細節 都被 封裝 在 類的內部
02. 小明跑步案例
需求:
-
小明 體重 50公斤
-
小明每次 跑步 會減肥 1公斤
-
小明每次 吃東西 體重增加 0.5 公斤
class Person(object):
def __init__(self, name, weight):
self.name = name
self.weight = weight
def __str__(self):
return ‘%s 體重 %.2f 公斤‘ % (self.name, self.weight)
def run(self):
print(‘%s 正在跑步‘ % self.name)
self.weight -= 1
def eat(self):
print(‘%s 正在吃東西‘ % self.name)
self.weight += 0.5
xiaoming = Person(‘xiaoming‘, 50)
print(xiaoming)
xiaoming.run()
xiaoming.eat()
print(xiaoming)
03. 擺放家具案例
需求
-
房子(House) 有 戶型、總面積 和 家具名稱列表
-
新房子沒有任何的家具
-
-
家具(HouseItem) 有 名字 和 占地面積,其中
-
席夢思(bed) 占地 4平米
-
衣櫃(chest) 占地 2平米
-
餐桌(table) 占地 1.5平米
-
-
將以上三件 家具 添加 到 房子 中
-
打印房子時,要求輸出:戶型、總面積、剩余面積、家具名稱列表
剩余面積
-
在創建房子對象時,定義一個 剩余面積的屬性,初始值和總面積相等
-
當調用 add_item方法,向房間 添加家具 時,讓 剩余面積 -= 家具面積
思考:應該先開發哪一個類?
答案 —— 家具類
-
家具簡單
-
房子要使用到家具,被使用的類,通常應該先開發
class HouseItem(object):
def __init__(self, name, area):
# name : 家具名稱
# area : 家具占地面積
self.name = name
self.area = area
def __str__(self):
return ‘%s 占地 %.2f 平米‘ % (self.name, self.area)
bed = HouseItem("席夢思", 4)
chest = HouseItem("衣櫃", 2)
table = HouseItem("餐桌", 1.5)
#print(bed)
#print(chest)
#print(table)
class House(object):
def __init__(self, house_type, area):
# housetype: 房子類型
# area: 房子總面積
self.house_type = house_type
self.area = area
self.free_area = area
self.house_item = list()
def __str__(self):
return ‘房子類型:%s,總面積:%.2f,剩余可用面積:%.2f,家具:%s‘ % (s
elf.house_type, self.area, self.free_area, self.house_item)
def add_item(self,item):
print("要添加:%s" %item)
if item.area > self.free_area:
print("剩余面積不足")
return
else:
self.house_item.append(item.name)
self.free_area -= item.area
my_home = House("小別野", 200)
my_home.add_item(bed)
my_home.add_item(chest)
my_home.add_item(table)
print(my_home)
04.身份運算符
身份運算符用於 比較 兩個對象的 內存地址 是否一致 —— 是否是對同一個對象的引用
- 在python中針對None比較時,建議使用 is 判斷
運算符 | 描述 | 實例 |
---|---|---|
is | is 是判斷兩個標識符是不是引用同一個對象 | x is y,類似 id(x) == id(y) |
is not | is not 是判斷兩個標識符是不是引用不同對象 | x is not y,類似 id(a) != id(b) |
is 與 == 區別:
is
用於判斷 兩個變量 引用對象是否為同一個 ==
用於判斷 引用變量的值 是否相等
>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> b is a
False
>>> b == a
True
二、繼承
-
單繼承
-
多繼承
面向對象三大特性
-
封裝 根據 職責 將 屬性 和 方法 封裝 到一個抽象的 類 中
-
繼承 實現代碼的重用,相同的代碼不需要重復的編寫
-
多態 不同的對象調用相同的方法,產生不同的執行結果,增加代碼的靈活度
01. 單繼承
1.1 繼承的概念、語法和特點
繼承的概念:子類 擁有 父類 的所有 方法 和 屬性
1) 繼承的語法
class 類名(父類名):
pass
-
子類 繼承自 父類,可以直接 享受 父類中已經封裝好的方法,不需要再次開發
-
子類 中應該根據 職責,封裝 子類特有的 屬性和方法
2) 專業術語
-
Dog類是 Animal類的子類,Animal類是Dog類的父類,Dog類從Animal類繼承
-
Dog類是 Animal類的派生類,Animal類是Dog類的基類,Dog類從Animal類派生
3) 繼承的傳遞性
-
C類從B類繼承,B類又從A類繼承
-
那麽C類就具有B類和A類的所有屬性和方法
子類 擁有 父類 以及 父類的父類 中封裝的所有 屬性 和 方法
1.2 方法的重寫
-
子類 擁有 父類 的所有 方法 和 屬性
-
子類 繼承自 父類,可以直接 享受 父類中已經封裝好的方法,不需要再次開發
應用場景
-
當 父類 的方法實現不能滿足子類需求時,可以對方法進行 重寫(override)
重寫 父類方法有兩種情況:
- 覆蓋 父類的方法
- 對父類方法進行 擴展
1) 覆蓋父類的方法
-
如果在開發中,父類的方法實現 和 子類的方法實現,完全不同
-
就可以使用 覆蓋 的方式,在子類中 重新編寫 父類的方法實現
具體的實現方式,就相當於在 子類中 定義了一個 和父類同名的方法並且實現
重寫之後,在運行時,只會調用 子類中重寫的方法,而不再會調用 父類封裝的方法
2) 對父類方法進行擴展
-
如果在開發中,子類的方法實現 中 包含 父類的方法實現
- 父類原本封裝的方法實現 是 子類方法的一部分
-
就可以使用 擴展 的方式
-
在子類中 重寫 父類的方法
-
在需要的位置使用 super(). 父類方法來調用父類方法的執行
-
代碼其他的位置針對子類的需求,編寫 子類特有的代碼實現
-
關於 super
-
在Python中super是一個 特殊的類
-
super()就是使用super類創建出來的對象
-
最常 使用的場景就是在 重寫父類方法時,調用 在父類中封裝的方法實現
調用父類方法的另外一種方式
在Python 2.x時,如果需要調用父類的方法,還可以使用以下方式:
父類名.方法(self)
-
這種方式,目前在Python 3.x還支持這種方式
-
這種方法 不推薦使用,因為一旦 父類發生變化,方法調用位置的 類名 同樣需要修改
提示
-
在開發時,父類名和super()兩種方式不要混用
-
如果使用 當前子類名 調用方法,會形成遞歸調用,出現死循環
1.3 父類的 私有屬性 和 私有方法
-
子類對象 不能 在自己的方法內部,直接 訪問 父類的 私有屬性 或 私有方法
-
子類對象 可以通過 父類 的 公有方法 間接 訪問到 私有屬性 或 私有方法
-
私有屬性、方法 是對象的隱私,不對外公開,外界 以及 子類 都不能直接訪問
-
私有屬性、方法 通常用於做一些內部的事情
-
B的對象不能直接訪問__num2屬性
-
B的對象不能在demo方法內訪問__num2屬性
-
B的對象可以在demo方法內,調用父類的test方法
-
父類的test方法內部,能夠訪問__num2屬性和 __test方法
02. 多繼承
概念
- 子類 可以擁有 多個父類,並且具有 所有父類 的 屬性 和 方法
- 例如:孩子 會繼承自己 父親 和 母親 的 特性
語法
class 子類名(父類名1, 父類名2...)
pass
2.1 多繼承的使用註意事項
-
如果 不同的父類 中存在 同名的方法,子類對象 在調用方法時,會調用 哪一個父類中的方法呢?
提示:開發時,應該盡量避免這種容易產生混淆的情況! —— 如果 父類之間 存在 同名的屬性或者方法,應該 盡量避免 使用多繼承
Python 中的 MRO —— 方法搜索順序
-
Python中針對 類 提供了一個 內置屬性 __mro__ 可以查看 方法 搜索順序
-
MRO 是method resolution order,主要用於 在多繼承時判斷 方法、屬性 的調用 路徑
print(C.__mro__)
輸出結果
(<class ‘__main__.C‘>, <class ‘__main__.A‘>, <class ‘__main__.B‘>, <class ‘object‘>)
-
在搜索方法時,是按照 __mro__ 的輸出結果 從左至右 的順序查找的
-
如果在當前類中 找到方法,就直接執行,不再搜索
-
如果 沒有找到,就查找下一個類 中是否有對應的方法,如果找到,就直接執行,不再搜索
-
如果找到最後一個類,還沒有找到方法,程序報錯
2.2 新式類與舊式(經典)類
object是Python為所有對象提供的 基類,提供有一些內置的屬性和方法,可以使用dir函數查看
-
新式類:以object為基類的類,推薦使用
-
經典類:不以object為基類的類,不推薦使用
-
在Python 3.x中定義類時,如果沒有指定父類,會 默認使用 object 作為該類的 基類 ——Python 3.x中定義的類都是 新式類
-
在Python 2.x中定義類時,如果沒有指定父類,則不會以object作為 基類
新式類 和 經典類 在多繼承時 —— 會影響到方法的搜索順序
為了保證編寫的代碼能夠同時在Python 2.x和Python 3.x運行!
今後在定義類時,如果沒有父類,建議統一繼承自object
class 類名(object):
pass
三、多態
面向對象三大特性
- 封裝 根據 職責 將 屬性 和 方法 封裝 到一個抽象的 類 中
-
定義類的準則
-
- 繼承 實現代碼的重用,相同的代碼不需要重復的編寫
-
設計類的技巧
-
子類針對自己特有的需求,編寫特定的代碼
-
-
多態 不同的 子類對象 調用相同的 父類方法,產生不同的執行結果
- 多態 可以 增加代碼的靈活度
- 以 繼承 和 重寫父類方法 為前提
- 是調用方法的技巧,不會影響到類的內部設計
案例
需求
-
在Dog類中封裝方法game
- 普通狗只是簡單的玩耍
-
定義XiaoTianDog繼承自Dog,並且重寫game方法
- 哮天犬需要在天上玩耍
-
定義Person類,並且封裝一個 和狗玩 的方法
-
在方法內部,直接讓 狗對象 調用game方法
案例小結
-
Person類中只需要讓 狗對象 調用game方法,而不關心具體是 什麽狗
-
game方法是在Dog父類中定義的
-
-
在程序執行時,傳入不同的 狗對象 實參,就會產生不同的執行效果
多態 更容易編寫出出通用的代碼,做出通用的編程,以適應需求的不斷變化!
class Dog(object): def __init__(self, name): self.name = name def game(self): print("%s 歡快的玩耍" % self.name) class XiaoTianQuan(Dog): def game(self): print("%s 在天上玩耍" % self.name) class Person(object): def __init__(self, name): self.name = name def game_with_dog(self, dog): print("%s 和 %s 愉快的玩耍" % (self.name, dog.name)) # 讓狗玩耍 dog.game() # 創建一個狗對象 xiaotianquan = XiaoTianQuan("哮天犬") # 創建一個人對象 xiaobai = Person("小白") # 小白與狗玩耍 xiaobai.game_with_dog(xiaotianquan)
面向對象封裝、繼承、多態