程式扎記: [Python 學習筆記] 函式、類別與模組 : 類別 (抽象類別)

標籤

2015年7月7日 星期二

[Python 學習筆記] 函式、類別與模組 : 類別 (抽象類別)

來源自 這裡 
Preface : 
定義類別,本身就是在進行抽象化,如果一個類別定義時不完整,有些狀態或行為必須留待子類別來具體實現,則它是個抽象類別Abstract Class)。例如,在定義銀行帳戶時,你也許想將 一些帳戶的共同狀態與行為定義在父類別中 : 
- 類別 Account : 
  1. class Account:  
  2.     def withdraw(self, amount):  
  3.         if amount <= self.balance:  
  4.             self.balance -= amount  
  5.         else:  
  6.             raise ValueError('Not enough balance')  
  7.   
  8.     def __str__(self):  
  9.         return ('Id:\t\t' + self.id +  
  10.                 '\nName:\t\t' + self.name +  
  11.                 '\nBalance:\t' + str(self.balance))  
顯然地,這個類別的定義不完整,self.id、self.name、self.balance沒有定義,嘗試使用這個類別進行操作時,就會發生直譯錯誤 : 
acct = Account()
print(acct)

Abstract Class : 
你可以繼承這個類別來實作未完整的定義 : 
- 類別 CheckingAccount : 
  1. class CheckingAccount(Account):  
  2.     def __init__(self, id, name):  
  3.         self.id = id  
  4.         self.name = name  
  5.         self.balance = 0  
  6.         self.overdraftlimit = 30000  
  7.   
  8.     def withdraw(self, amount):  
  9.         if amount <= self.balance + self.overdraftlimit:  
  10.             self.balance -= amount  
  11.         else:  
  12.             raise ValueError('Not enough credit')  
  13.   
  14.     def __str__(self):  
  15.         return (super(CheckingAccount, self).__str__()+  
  16.                 '\nOverdraft limit\t' + str(self.overdraftlimit))  
  17.   
  18. acct = CheckingAccount('E1223','Peter')  
  19. print(acct)  
現在的問題是,實際上開發人員還是可以用 Account() 實例化,也許您可以修改一下類別 Account 的定義 : 
  1. class Account:  
  2.     def __init__():  
  3.         raise NotImplementedError("Account is abstract")  
  4.           
  5.     ...略  
如此,嘗試使用 Account() 實例化後,在初始化方法中就會引發錯誤(不過,實際上 Account 實例確實有產生了,但就這邊的需求來說,目的算已達到). 

像 Python 這類的動態語言,沒有 Java 的 abstract 或 interface 這種機制來規範一個類別所需實作的介面,遵循物件之間的協定基本上是開發 人員的自我約束(當然,還得有適當的說明文件). 如果你非得有個方式,強制實現某個公開協定,那該怎麼作?像上面一樣,藉由直譯錯誤是一種方式,實際上視你的需求而定(是否可實例化、子類別是否定義初始化方法等),還有許多模擬的方式,不過在 Python 中,可以使用 Meta class 與 @abstractmethod 來達到規範的需求. 

舉個例子來說,您想要設計一個猜數字遊戲,猜數字遊戲的流程大致就是 : 
顯示訊息(歡迎)
隨 機產生數字
遊戲迴圈
----顯示訊息(提示使用者輸入)
----取得使用者輸入
----比較是否猜中
----顯示訊息(輸入正確與否)

在描述流程輸廓時,並沒有提及如何顯示訊息、沒有提及如何取得使用者輸 入等具體的作法,只是歸納出一些共同的流程步驟 : 
- 類別 GuessGame : 
  1. import random  
  2. from abc import ABCMeta, abstractmethod  
  3.   
  4. class GuessGame(metaclass=ABCMeta):  
  5.     @abstractmethod  
  6.     def message(self, msg):  
  7.         pass  
  8.   
  9.     @abstractmethod  
  10.     def guess(self):  
  11.         pass       
  12.   
  13.     def go(self):  
  14.         self.message(self.welcome)  
  15.         number = int(random.random() * 10)  
  16.         while True:  
  17.             guess = self.guess();  
  18.             if guess > number:  
  19.                 self.message(self.bigger)  
  20.             elif guess < number:  
  21.                 self.message(self.smaller)  
  22.             else:  
  23.                 break  
  24.         self.message(self.correct)  
現在GuessGame是個抽象類別,如果你嘗試實例化GuessGame : 
 

如果是個文字模式下的猜數字遊戲,可以將顯示訊息、取得使用者輸入等以文字模式下的具體作法實現出來. 例如 : 
- 類別 ConsoleGame : 
  1. class ConsoleGame(GuessGame):  
  2.     def __init__(self):  
  3.         self.welcome = "歡迎"  
  4.         self.prompt = "輸入數字:"  
  5.         self.correct = "猜中了"  
  6.         self.bigger = "你猜的比較大"  
  7.         self.smaller = "你猜的比較小"  
  8.       
  9.     def message(self, msg):  
  10.         print(msg)  
  11.       
  12.     def guess(self):  
  13.         return int(input(self.prompt))  
執行結果 : (假設 ConsoleGame, GuessGame 都已經定義在 GuessGame.py
 

如果子類別忘了實作某個方法,則該子類別仍被視為一個抽象類別,如果嘗試實例化抽象類別就會引發錯誤。例如若忘了實作 message(),就會發生以下錯誤 : 
TypeError: Can't instantiate abstract class ConsoleGame with abstract methods message

所以,如果你真的想要模擬Java中interface的作用,則可以定義一個抽象類別,完全沒有實作的方法即可. 例如 : 
  1. import random  
  2. from abc import ABCMeta, abstractmethod  
  3.   
  4. class Flyer(metaclass=ABCMeta): # 就像是Java中的interface  
  5.     @abstractmethod  
  6.     def fly(self):  
  7.         pass  
  8.   
  9. class Bird:  
  10.     pass  
  11.       
  12. class Sparrow(Bird, Flyer):  # 就像Java中繼承Bird類別並實作Flyer介面  
  13.     def fly(self):  
  14.         print('麻雀飛')  
  15.   
  16. s = Sparrow()  
  17. s.fly()  
Supplement 
Python Standard Library: abc — Abstract Base Classes

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!