Python 入門指南 5.0

單元 16 - 靜態方法與抽象方法

~~學習進度表~~

靜態方法 (static method) 是類別 (class) 中沒有預設參數 (parameter) 的方法 (method) ,抽象方法 (abstract method) 則是不實作內容,由繼承 (inheritance) 的子類別 (subclass) 來實作

@staticmethod
@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

abcabstract 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() 印出狼不能過河的訊息。 參考程式碼

上一頁 單元 15 - 繼承與多型
回 Python 入門指南 5.0 首頁
下一頁 單元 17 - 型態標記與檢查資料型態
回 Python 教材首頁
回程式語言教材首頁