六、Python基礎(封裝、繼承、多型)
目錄:
- 六、Python基礎(封裝、繼承、多型)
- 一、繼承與多型
- 1.單繼承
- 2.方法的重寫
- super().父類方法
- 3.父類的私有屬性和私有方法
- 4.多繼承
- print(子類.\_\_mro__)
- 5.多型
- 6.術語
- 二、類屬性和方法
- 1.類屬性
- 類名.類屬性
- 實體名.類屬性
- 2.@classmethod類方法
- 類名.類方法名(cls, 引數串列)
- 3.@staticmethod靜態方法
- 類名.靜態方法名(引數串列)
- 4.階段小結:游戲計分板
- 5.補充:當方法要同時訪問類屬性和實體屬性時
- 三、單例
- 1.單例設計模式
- 2.\_\_new__ 方法
- 3.Python中的單例
- 上一篇文章
- 下一篇文章
面向物件的三大屬性:
- 封裝 根據職責將屬性和方法封裝在一個個類中,并基于這些類來創建物件
- 繼承 實作代碼的復用,相同的代碼不需要重復撰寫
- 多型 不同的物件呼叫相同的方法,產生不同的執行結果,增加代碼的靈活度
一、繼承與多型
1.單繼承
如圖:使用繼承時,最左端為父類,右端為子類(圖摘自黑馬程式員)

單繼承的代碼結構如圖所示:

- 子類繼承擁有了父類的所有屬性和方法,可以直接享用,無須再次開發
- 子類里可額外增加父類里沒有的屬性和方法
- 子類會全部繼承父類的所有屬性和方法,并且可以在 父——子 之間不斷傳遞下去,即子類繼承的屬性和方法會越來越多,越來越強大——進化論
專業術語,在上圖中:
- Dog類 是 Animal類 的子類,Animal類 是 Dog類 的父類
- Dog類 是 Animal類 的派生類,Animal類 是 Dog類 的基類
例:Animal 是父類,Dog是子類,Dog類 可以享用 Animal類 中的方法和屬性
class Animal:
def eat(self):
pass
def run(self):
pass
def drink(self):
pass
class Dog(Animal):
def shout(self):
pass
2.方法的重寫
(1)情況1:覆寫父類的方法
有時候,子類的某些屬性或方法的實作可能并不想和父類相同,如子狗的毛色和父狗的毛色可能不一樣,這個時候就需要用 方法的重寫 來覆寫父類中的屬性或方法,如圖:
覆寫方法:直接在子類中重新撰寫與 “需要覆寫的方法” 同名的方法即可

在重寫之后,在子類物件呼叫方法或屬性時,只會呼叫子類重寫后的方法(因為已覆寫)
例:
class Animal:
def eat(self):
pass
def run(self):
pass
class Dog(Animal):
def run(self):
pass
(2)情況2:對父類的方法進行擴展
對于父類的一個方法,若子類除 父類原有方法的實作 需要實作外,仍有另一部分也需要實作(而父類沒有的那部分),則子類需要在父類的方法的基礎上進行擴展
super().父類方法
可以在覆寫父類方法后的方法里,呼叫原父類方法,實作父類方法的再現
我們只需先覆寫掉原父類的方法,寫入需要擴展的代碼;再在覆寫父類方法的基礎上,在需要的位置利用 super().父類方法 呼叫回原父類方法(父類方法的再現)即可
例:
class Animal:
def eat(self):
pass
def run(self):
pass
class Dog(Animal):
def run(self):
"""另外補充需要擴展的代碼即可"""
super().run()
"""另外補充需要擴展的代碼即可"""
pass
3.父類的私有屬性和私有方法
- 子類物件不能在自己的方法內部,直接訪問父類的私有屬性和私有方法
- 子類物件可以通過 父類的公有方法 間接訪問到父類的私有屬性和私有方法
例:通過父類的公有方法間接訪問到父類的私有屬性和私有方法
class Animal:
"""定義屬性"""
def __init__(self):
self.__name = "狗蛋"
self.__variety = "Huskie"
"""定義一個私有方法"""
def __run(self):
print("訪問私有方法:這是一個父類的私有方法")
"""定義一個訪問父類私有屬性的方法"""
def visit(self):
print("訪問私有屬性:__name:%s,__variety:%s" % (self.__name, self.__variety))
self.__run()
class Dog(Animal):
def text(self):
self.visit()
example = Dog()
example.text()

4.多繼承
- 子類可以擁有多個父類,并且具有所有父類的屬性和方法
多繼承的代碼結構如圖所示:

多繼承使用的注意事項:
- 如果不同的父類中存在同名的方法,子類物件在呼叫父類的方法時,會呼叫哪個父類的方法呢?
- 在開發中,應該盡量避免上述這種容易造成混淆的情況,如果父類之間存在同名的屬性或方法時,應該盡量避免使用多繼承
模糊判斷:一般是按定義多繼承時 class 子類(父類1…) 從左至右的順序查找
*對上述問題呼叫優先級的清晰解答——MRO方法搜索順序(了解):
Python中針對類提供了一個內置屬性 __mro__ 可以查看方法搜索順序,主要用與在多繼承時判斷方法、屬性的呼叫路徑
print(子類.__mro__)
顯示呼叫方法/屬性時的順序,從左到右顯示的父類來順序查找,找到即執行,找不到即向右查找
其中,<class ‘object’> 中,object類是所有類的基類
object類是一些物件通用的方法,如:字串的方法…
在定義父類時,可能會看到有 class 父類(object): ,這是因為在Python3.X中,類默認是基于object的新類來定義的,在Python2.X中,默認用的是經典類,一般不推薦使用經典類,因此在創建父類時,可以加上(object)
5.多型
不同的子類物件呼叫相同的父類方法時,可以通過方法的重寫和繼承來產生不同的執行效果
在其他類創建的物件中呼叫多型類創建的物件時:其他類創建的物件呼叫多型類創建的物件,由于方法/屬性的重寫的繼承,呼叫相同的方法可能產生不同的執行結果,形成多型
- 呼叫相同的方法可能產生不同的執行結果,形成多型(重要)
例:結構示意圖

class Transportation:
"""運輸父類"""
def __init__(self, vehicle):
"""屬性:交通工具名"""
self.vehicle = vehicle
def arrive(self):
print("%s成功到達指定地點" % self.vehicle)
class Plane(Transportation):
"""飛機子類,基于運輸父類"""
def arrive(self):
"""方法的重寫"""
print("%s以最快的速度到達指定地點" % self.vehicle)
class Person:
"""人類"""
def __init__(self, name):
"""屬性:人名"""
self.name = name
def choice(self, vehicle_choice):
"""選擇的交通工具"""
vehicle_choice.arrive()
"""波士頓:飛機"""
Boston = Plane("Boston")
"""泰坦尼克號:輪船"""
Titanic = Transportation("Titanic")
"""約翰:人"""
John = Person("John")
"""約翰選擇不同的交通工具,會帶來不同的結果,形成多型"""
John.choice(Boston)
John.choice(Titanic)

6.術語
- 基于類創建的物件通常也稱為類的實體
- 創建物件的程序通常也稱為實體化
- 物件的屬性通常也稱為實體屬性
- 物件呼叫的方法通常也稱為實體方法
在Python中,一切皆物件,類是一個特殊的物件,class 屬于類物件,object = class() 屬于實體物件,因此Python同樣會為類分配記憶體
二、類屬性和方法
1.類屬性
在創建類時,我們會用 __init__ 來定義物件的屬性,但另外,類也有它的屬性,類的屬性不需要寫在方法內,而直接寫在類的內部(class定義類的下方)
類屬性就是給類物件中定義的屬性,通常用來記錄與這個類相關的特征,類屬性不會用于記錄具體物件的特征
例:
class Tool:
"""使用賦值陳述句,定義類屬性,記錄創建工具物件的總數"""
count = 0
def __init__(self, name):
self.name = name
"""每創建一個類,做一個計數"""
Tool.count += 1
tool1 = Tool("錘子")
tool2 = Tool("扳手")
tool3 = Tool("斧頭")
print("可以用類名來訪問類屬性:%d" % Tool.count)
for temp_object in (tool1, tool2, tool3):
print("而且可以通過物件(實體)來訪問類屬性:%d" % temp_object.count)

- 對于通過物件名來訪問屬性的——Python優先查找物件屬性,若物件屬性中不存在,則訪問類屬性(即可能存在物件屬性和類屬性同名的情況)
- 通過類名來訪問屬性的,則只會查找類屬性,因此在查找類屬性時,不推薦通過物件名來查找屬性
類名.類屬性
可以在類內部或外部訪問到類屬性
實體名.類屬性
也可以訪問到類屬性,但不推薦
注:賦值陷阱
在類的外部可以通過 物件.屬性 = 屬性值 會給物件定義一個物件屬性
2.@classmethod類方法
類方法就是針對類的物件定義的方法
定義類方法的代碼結構如圖所示:

- 類方法需要用 修飾器@classmethod 來標識,告訴解釋器這是一個類方法
- 類方法的第一個引數應該是 cls
- 由哪一個類呼叫的方法,方法內的 cls 就是哪一個類的參考,與 self 是十分類似的
- cls 可以用其他名稱代替,但是習慣上用 cls
類名.類方法名(cls, 引數串列)
可以呼叫類方法
注:cls 不傳遞引數
例:
class Tool:
"""使用賦值陳述句,定義類屬性,記錄創建工具物件的總數"""
count = 0
"""定義一個類方法,用于列印工具的數量count"""
@classmethod
def show_tool_count(cls):
print("工具物件的總數為:%d" % cls.count)
def __init__(self, name):
self.name = name
"""每創建一個類,做一個計數"""
Tool.count += 1
tool1 = Tool("錘子")
tool2 = Tool("扳手")
tool3 = Tool("斧頭")
Tool.show_tool_count()

3.@staticmethod靜態方法
在開發時,如果需要在類中封裝一個方法,這個方法:
- 既不需要訪問實體屬性或者呼叫實體方法
- 也不需要訪問類屬性或者呼叫類方法
這個時候,可以把這個方法封裝成類方法
定義靜態方法的代碼結構如圖所示:

- 靜態方法需要用 修飾器@staticmethod 來標識,告訴解釋器這是一個靜態方法
類名.靜態方法名(引數串列)
可以通過呼叫靜態方法
4.階段小結:游戲計分板
需求:
- 設計一個Game類
- 其中包含兩個屬性:
定義一個類屬性:top_score 記錄游戲的歷史最高分
定義一個實體屬性:player_name 記錄當前游戲的玩家姓名 - 其中包含三個方法:
定義一個靜態方法:show_help 顯示游戲幫助資訊
定義一個類方法:show_top_score 顯示歷史最高分
實體方法:start_game 開始當前玩家的游戲 - 主程式步驟:
(1)查看游戲幫助資訊
(2)查看游戲歷史最高分——最高分玩家
(3)創建游戲物件,開始游戲
class Game(object):
"""Game類"""
"""類屬性:歷史最高分"""
top_score = 0
"""類屬性:最高分玩家"""
top_player: str = None
def __init__(self, player_name):
"""實體屬性:當前游戲的玩家名"""
self.player_name = player_name
@staticmethod
def show_help():
print("--這是游戲幫助資訊--")
@classmethod
def show_top_score(cls):
print("歷史最高分:%d,玩家:[%s]" % (cls.top_score, cls.top_player))
def start_game(self):
print("游戲開始")
temp_score = int(input("玩家[%s]的最終分數是:"))
if temp_score >= Game.top_score:
print("恭喜[%s],游戲分數:%d,突破游戲記錄" % (self.player_name, temp_score))
Game.top_score = temp_score
Game.top_player = self.player_name
else:
print("[%s]的最終得分是:%d" % (self.player_name, temp_score))
"""呼叫類方法無需創建物件"""
Game.show_help()
Game.show_top_score()
lcx = Game("~憲憲")
lcx.start_game()
lcx.show_top_score()

5.補充:當方法要同時訪問類屬性和實體屬性時
如果方法內部既需要訪問類屬性,又要訪問實體屬性時,這個方法應該定義成實體方法
- 實體方法 方法內部需要訪問實體屬性,也可以訪問類屬性
- 類方法 方法內部只需要訪問類屬性
- 靜態方法 方法內部不需要訪問類屬性和實體屬性
三、單例
1.單例設計模式
所謂設計模式,即前人的作業總結和提煉,通常,被人們廣泛流傳的設計模式都是針對某一特定問題的成熟的解決方案——套路
使用設計模式是為了可復用代碼、讓代碼易讀性更高、保證代碼的可靠性
單例設計模式:
- 目的 讓類創建的物件,在系統中只有唯一的一個實體
- 每一次執行 類名() 回傳的物件,記憶體地址是相同的
應用場景:
- 音樂播放物件:同時只能播放一首音樂(一個實體/物件)
- 回收站物件:所有被清理的軟體都被放入同一回收站(一個實體/物件)
- 列印機物件:列印機只能同時列印一份檔案(一個實體/物件)
上述例子都符合只有唯一的一個實體的單例設計模式
2.__new__ 方法
- 使用 類名() 創建物件時,Python解釋器會首先呼叫 __new__ 方法為物件分配記憶體空間
- __new__ 方法是一個由 object 基類提供的內置靜態方法,主要作用有兩個:
在記憶體中為物件分配空間
回傳物件的參考 - 重寫 __new__ 方法的代碼十分固定
- 重寫 __new__ 方法一定要 return super().__new__(cls),否則Python解釋器得不到分配了空間的物件參考
- super() 可以訪問父類,因此 super().__new__(cls) 實際上是訪問基類的 __new__ 方法,用于分配一片記憶體空間
- __new__ 是一個靜態方法,在呼叫時需要主動傳遞 cls 引數
單例——重寫 __new__ 方法:
例:
class MusicPlayer(object):
def __new__(cls, *args, **kwargs):
print("創建物件,分配空間")
return super().__new__(cls)
def __init__(self):
print("音樂播放器初始化")
"""創建播放器物件"""
player = MusicPlayer()
print(player)

3.Python中的單例
讓類創建的物件,在系統中只有唯一的一個實體:
- 重寫 __new__ 方法 ,其中:
如果類屬性 is None,呼叫父類方法分配空間,并在類屬性中獲得回傳結果 - 回傳類屬性中記錄的物件參考

(圖摘自黑馬程式員)
提示:如果已經首次創建過實體了,則上述類屬性就不為None了,因此只要不重新分配空間,instance 就一直是首次創建實體時的參考,直接回傳 instance,即直接回傳首次創建實體時的參考,那么每次創建物件時的地址都是唯一的,實作單例
例:
class MusicPlayer(object):
"""音樂播放器"""
"""記錄實體的參考情況"""
instance = None
def __new__(cls, *args, **kwargs):
"""如果沒有實體,就呼叫父類方法分配空間"""
if cls.instance is None:
cls.instance = super().__new__(cls)
"""如果已有實體,就分配記憶體,直接回傳原有實體,實作地址唯一"""
return cls.instance
def __init__(self):
print("音樂播放器初始化")
"""創建多個物件驗證地址是否相同"""
player1 = MusicPlayer()
print(player1)
player2 = MusicPlayer()
print(player2)


冗余性解決:既然單例場景中創建的物件都是同一個實體,那么如果每次創建一個實體,類中每次都會自動進行一次 __init__ 初始化動作,造成冗余,那么有沒有一種辦法,可以讓初始化動作只被執行一次呢?
解決方法:
- 定義一個類屬性 init_flag,標記是否執行過初始化動作,初始值為 False
- 在 __init__ 方法中,判斷 init_flag 是否為 False,是則執行初始化動作,然后將 init_flag 設定為 True,限制下一次初始化動作,這樣,當下一次再創建實體時,就不會再執行初始化動作了
例:
class MusicPlayer(object):
"""音樂播放器"""
"""記錄實體的參考情況"""
instance = None
"""記錄是否執行過初始化方法"""
init_flag = False
def __new__(cls, *args, **kwargs):
"""如果沒有實體,就呼叫父類方法分配空間"""
if cls.instance is None:
cls.instance = super().__new__(cls)
"""如果已有實體,就分配記憶體,直接回傳原有實體,實作地址唯一"""
return cls.instance
def __init__(self):
if not MusicPlayer.init_flag:
print("執行初始化動作")
MusicPlayer.init_flag = True
"""創建多個物件驗證地址是否相同"""
player1 = MusicPlayer()
player2 = MusicPlayer()
player3 = MusicPlayer()
于是,我們即使創建了多個實體, __init__ 初始化動作也只被執行一次了

上一篇文章
- 五、Python基礎(類與物件)
下一篇文章
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/258373.html
標籤:其他
下一篇:生如夏花——一篇很美的詩詞
