Python 入門指南 5.0
單元 16 - 靜態方法與抽象方法
靜態方法 (static method) 是類別 (class) 中沒有預設參數 (parameter) 的方法 (method) ,抽象方法 (abstract method) 則是不實作內容,由繼承 (inheritance) 的子類別 (subclass) 來實作
@abc.abstractmethod
下面簡單示範如何定義靜態方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | # 定義具有靜態方法的類別
class ClassDemo13:
# 定義靜態方法
@staticmethod
def do_something():
print("呼叫靜態方法")
# 以下是執行部分
# 利用類別呼叫靜態方法
ClassDemo13.do_something()
# 利用實體物件呼叫靜態方法
d = ClassDemo13()
d.do_something()
# 檔名: class_demo17.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月
|
定義靜態方法是在方法定義的上一行加入裝飾子 (decorator) @statemethod ,注意靜態方法不需要預設的參數
4 5 6 | @staticmethod
def do_something():
print("呼叫靜態方法")
|
底下分別用類別名稱及實體 (instance) 物件呼叫靜態方法
10 12 13 | ClassDemo13.do_something()
d = ClassDemo13()
d.do_something()
|
執行結果如下
$ python class_demo17.py |
呼叫靜態方法 |
呼叫靜態方法 |
$ |
也就是說,靜態方法跟類別方法 (class method) 極為類似,但是靜態方法沒有預設參數,因此不能直接使用類別屬性 (class attribute) ,以下示範如何在靜態方法中使用實體屬性 (instance attribute)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | # 定義具有靜態方法的類別
class ClassDemo14:
# 定義建構子
def __init__(self):
# 定義實體屬性
self.a = "實體屬性"
# 定義靜態方法
@staticmethod
def do_something():
# 建立同類別的物件實體
d = ClassDemo14()
# 印出實體屬性
print(d.a)
# 以下是執行部分
# 利用類別呼叫靜態方法
ClassDemo14.do_something()
# 檔名: class_demo18.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月
|
在靜態方法中如果要使用同類別定義的實體屬性,必須建立同類別的實體物件
9 10 12 14 | @staticmethod
def do_something():
d = ClassDemo14()
print(d.a)
|
底下簡單用類別名稱呼叫靜態方法,此例執行結果如下
$ python class_demo18.py |
實體屬性 |
$ |
接下來繼續以簡單例子示範在靜態方法中使用類別屬性與類別方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | # 定義具有靜態方法的類別
class ClassDemo15:
# 定義類別屬性
class_attribute = "類別屬性"
# 定義類別方法
@classmethod
def class_method(cls):
# 回傳類別方法
return "類別方法"
# 定義靜態方法
@staticmethod
def do_something():
# 印出類別屬性
print(ClassDemo15.class_attribute)
# 印出類別方法的回傳值
print(ClassDemo15.class_method())
# 以下是執行部分
# 利用類別呼叫靜態方法
ClassDemo15.do_something()
# 檔名: class_demo19.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月
|
如果要在靜態方法中使用類別屬性或類別方法,同樣用類別識別字 (identifier) 呼叫
13 14 16 18 | @staticmethod
def do_something():
print(ClassDemo15.class_attribute)
print(ClassDemo15.class_method())
|
底下同樣簡單用類別名稱呼叫靜態方法,此例執行結果如下
$ python class_demo19.py |
類別屬性 |
類別方法 |
$ |
靜態方法跟類別方法很相似,共通點是都屬於類別,必須用類別名稱或實體物件呼叫,差別是類別方法可以直接使用類別屬性或其他類別方法,因此如果在同類別的名稱下,所定義的方法不需要類別的其他定義,靜態方法就會是個好選擇。
至於抽象方法是指沒有被實作的方法,也就是抽象方法底下沒有實際可執行的程式工作,這需要標準程式庫 (standard library) 中的 abc 模組,舉一個簡單例子如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | # 引入標準程式中的 abc
import abc
# 定義具有抽象方法的類別
class ClassDemo16(abc.ABC):
# 定義抽象方法
@abc.abstractmethod
def do_something(self):
# 發起未實作的例外
raise NotImplemented
# 實作具有抽象方法的類別
class ClassDemo17(ClassDemo16):
# 實作抽象方法
def do_something(self):
print("實作抽象方法")
# 以下是執行部分
# 建立物件變數 d
d = ClassDemo17()
# 呼叫子類別實作的抽象方法
d.do_something()
# 檔名: class_demo20.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月
|
首先在第 2 行引入 abc
2 | import abc
|
abc 是 abstract base class 的頭字母縮寫詞。
然後具有抽象方法的類別要繼承 abc.ABC 類別
5 | class ClassDemo16(abc.ABC):
|
定義抽象方法是要在方法定義的上一行加入裝飾子 @abc.abstractmethod
2 8 10 | @abc.abstractmethod
def do_something(self):
raise NotImplemented
|
注意在 do_something() 方法底下縮排的部分是用關鍵字 raise 發起未實作的 NotImplemented 例外 (exception) 。
這樣繼承 ClassDemo16 的類別 ClassDemo17 就必須實作 do_something() 方法
13 15 16 | class ClassDemo17(ClassDemo16):
def do_something(self):
print("實作抽象方法")
|
執行部分就是以子類別 ClassDemo17 的實體物件 (object) 呼叫 do_somehting() 方法
20 22 | d = ClassDemo17()
d.do_something()
|
執行結果如下
$ python class_demo20.py |
實作抽象方法 |
$ |
抽象方法的運用場景是在類別設計時,有些子類別必須改寫 (override) 父類別 (superclass) 的方法,卻又不需要父類別的程式碼,因此定義抽象類別就直接要求子類別必須實作,沒有實作就會發生例外的錯誤。
如果類別中單純不實作可以用關鍵字 pass 帶過,但是這樣繼承後也無須實作,所以如果一定要求子類別實作,那就要定義抽象方法。
類別與物件導向的介紹大致到這裡結束,接下來我們來看看怎麼在 Python 中做型態標記 (type annotation) ,然後再討論函數多載 (overloading) 的程式設計技巧。
中英文術語對照 | |
---|---|
抽象方法 | abstract method |
類別 | class |
類別屬性 | class attribute |
類別方法 | class method |
例外 | exception |
裝飾子 | decorator |
識別字 | identifier |
繼承 | inheritance |
實體 | instance |
實體屬性 | instance attribute |
方法 | method |
物件 | object |
改寫 | override |
多載 | overloading |
參數 | parameter |
靜態方法 | static method |
標準程式庫 | standard library |
子類別 | subclass |
父類別 | superclass |
型態標記 | type annotation |
重點整理 |
---|
1. 定義靜態方法需要加上 @staticmethod ,這是類別中沒有預設參數的方法。 |
2. 如果要在靜態方法中使用實體屬性,需要在靜態方法中建立實體物件。 |
3. 定義抽象方法需要從逼準程式庫引入 abc ,然後在方法定義加上 @abc.abstractmethod ,裡頭要用 raise 發起 NotImplemented 。 |
問題與討論 |
---|
1. 靜態方法跟類別方法以什麼不同? |
2. 要怎麼在靜態方法中使用類別屬性或類別方法? |
3. 為什麼要定義給子類別實作的抽象方法? |
練習 |
---|
1. 承接單元 12 的範例 Demo2 類別,將新程式寫在 exercise1601.py 中,將類別方法改為靜態方法。 參考程式碼 |
2. 承上題,將新程式寫在 exercise1602.py 中,將 Demo3 類別方法改為靜態方法。 參考程式碼 |
3. 靜態方法可以用作通用的方法,寫一個新程式 exercise1603.py ,定義 Exercise1603 類別,裡頭用靜態方法 print_meaasge() 印出參數,然後用實體方法 print_hello() 印出指定的參數 "Hello" 。 參考程式碼 |
4. 承上題,將新程式寫在 exercise1604.py 中,裡頭定義類別屬性 count ,然後用靜態方法 add_1() 將 count 遞增,實體方法 add_p() 依參數增加 count 值。 參考程式碼 |
5. 承上題,將新程式寫在 exercise1605.py 中,增加將 count 遞減的靜態方法 substract_1() ,以及依參數減少 count 值的實體方法 substract_p() 。 參考程式碼 |
6. 承接上一個單元的練習 2 ,將新程式寫在 exercise1606.py 中,鬥獸棋的部分棋子可以過河,替 Animal 增加過河的抽象方法 cross_river() 。 參考程式碼 |
7. 承上題,將新程式寫在 exercise1607.py 中,鬥獸棋的獅子可以縱向或橫向的跳過河,重新定義 Lion 類別,並實作 cross_river() ,依參數進行縱向或橫向的過河。 參考程式碼 |
8. 承上題,將新程式寫在 exercise1608.py 中,鬥獸棋的老虎可以橫向的跳過河,重新定義 Tiger 類別,並實作 cross_river() ,依參數進行橫向左或右的過河。 參考程式碼 |
9. 承上題,將新程式寫在 exercise1609.py 中,鬥獸棋的老鼠可以進入河中游泳,重新定義 Mouse 類別,並實作 cross_river() ,讓老鼠可以游泳,並以實體屬性 in_river 記錄老鼠是否在河中。 參考程式碼 |
10. 承上題,將新程式寫在 exercise1610.py 中,鬥獸棋的狼並不能過河,重新定義 Wolf 類別,在 cross_river() 印出狼不能過河的訊息。 參考程式碼 |