今天續奏昨天的未盡琴音:
還記得上篇的abstract classes抽象類別
的定義嗎?類別中只要有一個抽象方法,該類別就是抽象類別。
和抽象類別相對,完全沒有抽象方法的,稱為concrete classes實體類別
(或曰具體類別、具象類別、實際類別,甚而搞笑版的混凝土類別)。
換句話說:
繼承自抽象類別的子類別,必須實作父類別的所有抽象方法,否則子類別自己也變成抽象,無法建立實例。不過這裡的實作算是廣義的,只要定義出同名方法就算,內容給它pass
或...
也行。
抽象(父)類別設計:
from abc import ABC, abstractmethod
class Tree(ABC): # 抽象方法
def __init__(self, breed: str):
self.__breed = breed
@property
def breed(self):
return self.__breed
@abstractmethod # 抽象方法
def provide_food(self):
...
@abstractmethod # 抽象方法
def help_breathe(self):
...
@abstractmethod # 抽象方法
def conserve_water(self):
...
如果子類別完全不實作:
class WeirdTree(Tree):
...
試圖建立一棵WeirdTree時:
try:
tree = WeirdTree('dragon blood')
except Exception as e:
print(str(e))
吃了個閉門羹:
別以為子類別擴增其他方法,就可以「蒙混過關」。沒用的,父類別的抽象方法一個都不能少:
class WeirdTree(Tree):
def provide_food(self): # 實作父類別的抽象方法(名稱)。
... # 內容則可以不實作出來。
def help_breathe(self): # 實作父類別的抽象方法(名稱)。
...
# 少了conserve_water(self)
def protect_land(self): # 自己擴增的方法。
print(f'I am a {__class__.__name__} {self.breed}. I protect the land.')
子類別實作了provide_food()
和help_breathe()
,也新增一個父類別沒有的 protect_land()
方法,卻漏掉conserve_water()
。結果還是無法建立實例:
子類別唯有實作出父類別的所有抽象方法,成為實體類別,才可以建立自己的物件。這裡的「實作」,是只要定義方法名稱就算。signature相不相同無所謂,方法的內容也可以用pass或三個點(Ellipsis)略過。
class WeirdTree(Tree):
def provide_food(self, nutrients: dict): # 必須有父類別的抽象方法。signature不同沒有關係,名稱相同就行。
... # 可以不實作內容。
def help_breathe(self, freshing_index: int=-1): # 必須有父類別的抽象方法。
print(f'I am a {__class__.__name__} {self.breed}. My air freshing index is {freshing_index}.')
def conserve_water(self, conserving_index, improve_water_quality: bool): # 必須有父類別的抽象方法。
print(f'I am a {__class__.__name__} {self.breed}. My water conserving index is {conserving_index}. I {"can" if improve_water_quality else "cannot"} improve water quality.')
def protect_land(self): # 子類別擴充,增加自己的方法。
print(f'I am a {__class__.__name__} {self.breed}. I protect the land.')
子類別終於可以建立的實例(物件了):
super()
呼叫:
from abc import ABC, abstractmethod
class Tree(ABC): # 這裡的「樹」採廣義,包括灌木(shrub)。
def __init__(self, breed: str):
self.__breed = breed
print(f"I am the constructor of class {__class__.__name__}. My breed is '{self.breed}'.")
@property
def breed(self):
return self.__breed
@abstractmethod
def provide_food(self):
...
@abstractmethod
def help_breathe(self): # abstract method原來也可以實作!
print(f'I am an abstract method in abstract class {__class__.__name__}.')
@abstractmethod
def conserve_water(self):
...
def provide_shelter(self):
print(f'I am a concrete method in abstract class {__class__.__name__}.')
class WeirdTree(Tree):
def provide_food(self, nutrients: dict):
...
def help_breathe(self, freshing_index: int=-1):
...
def conserve_water(self, conserving_index, improve_water_quality: bool):
...
def protect_land(self): # 子類別擴充的方法。
super() # 呼叫(抽象)父類別的constructor。
super().provide_shelter() # 呼叫(抽象)父類別的實體方法。
super().help_breathe() # 呼叫(抽象)父類別的抽象方法,可以嗎?
print()
print(f'I am a {__class__.__name__} {self.breed}. I protect the land.')
try:
tree = WeirdTree('bottle')
tree.protect_land()
except Exception as e:
print(str(e))
super()
呼叫(抽象)父類別的建構子(constructor)。super().xxx()
呼叫父類別的實體方法。help_breathe()
就實作了內容。super()
呼叫。上例WeirdTree
類別的protect_land()
方法就呼叫了父類別的抽象方法super().help_breathe()
。from abc import ABC, abstractmethod
class Tree(ABC):
def __init__(self, breed: str): # constructor
self.__breed = breed
self.__water = -1
@property
def breed(self) -> str: # 一般的property(getter)。
return self.__breed
@property
def water(self) -> int: # 一般的property(getter)。
return self.__water
@property
def water_given(self) -> int: # 一般的property(getter)。
return self.__water
@water_given.setter # water_given的setter property。
def water_given(self, water: int):
if water <= self.water_limit:
self.__water = water
else:
raise ValueError(f"You can't water a {self.__breed} with {water} cc.")
@property # 注意兩個裝飾器的先後次序。
@abstractmethod # 這個就是abstract property。
def water_limit(self): # 意思是「給水量上限」。
...
@abstractmethod
def watered(self): # 意思是「澆了多少水給這棵樹」,也是abstract唷。
...
class Hardwood(Tree):
@property # 要註明是property,否則視作一般方法,就不對了。
def water_limit(self) -> int: # 實作父類別的abstract property。
return 3000
def watered(self) -> None: # 實作父類別的abstract method。
... # 實際動作略。
print(f"Watering a {self.breed} with {self.water} cc.")
class Conifer(Tree):
@property # 要註明property,否則視作一般方法,就不對了。
def water_limit(self) -> int: # 實作父類別的abstract property。
return 2000
def watered(self) -> None: # 實作父類別的abstract method。
... # 實際動作略。
print(f"Watering a {self.breed} with {self.water} cc.")
try:
tree1 = Hardwood('maple')
tree1.water_given = 3_000
tree1.watered()
print()
tree2 = Conifer('cedar')
tree2.water_given = 2_200 # 這行發揮property功能,攔阻超過上限的給水值。
tree2.watered()
except Exception as e:
print(str(e))
water_limit(self)
(水量上限)和watered(self)
(受水)兩個都是抽象方法。water_limit(self)
既是抽象方法,也是property。要注意的是兩個裝飾器的先後次序,@property
必須寫在@abstractmethod
的上面,顛倒過來會引發錯誤。Tree
會有哪些共同性質和行為呢?屬性大概就是樹種、樹齡、樹高等,行為(方法)大概會有生長、傳宗接代、生病和死亡等。而不同的樹會有不同行為,如闊葉樹會落葉、會開花,而針葉樹因市場價值較高(假設),會有交易行為。分析完後,各個類別大致可以這樣設計:
from abc import ABC, abstractmethod
class Tree(ABC):
def __init__(self, breed: str, age: int, height: int): # constructor
self.__breed = breed
self.__age = age
self.__height = height
@property
def breed(self) -> str:
return self.__breed
@abstractmethod
def grow(self): # 生長(樹的共同行為)
...
@abstractmethod # 繁殖(樹的共同行為)
def reproduce(self):
...
@abstractmethod # 生病(樹的共同行為)
def get_sick(self):
...
@abstractmethod # 死亡(樹的共同行為)
def die(self):
...
class Hardwood(Tree): # Inherited from Tree
... # construtor略
def grow(self): # 實作父類別的生長行為。
print(f'{__class__.__name__} {self.breed} is growing.')
def reproduce(self): # 實作父類別的繁殖行為。
print(f'{__class__.__name__} {self.breed} is reproducing.')
def get_sick(self): # 實作父類別的生病行為。
print(f'{__class__.__name__} {self.breed} is getting sick.')
def die(self): # 實作父類別的死亡行為(假設死亡也有「行為」)。
print(f'{__class__.__name__} {self.breed} is dying.')
def defoliate(self): # 自己的行為:落葉
print(f'{__class__.__name__} {self.breed} is defoliating.')
def bloom(self): # 自己的行為:開花
print(f'{__class__.__name__} {self.breed} is blooming.')
class Conifer(Tree): # Inherited from Tree
... # construtor略
def grow(self):
print(f'{__class__.__name__} {self.breed} is growing.')
def reproduce(self):
print(f'{__class__.__name__} {self.breed} is reproducing.')
def get_sick(self):
print(f'{__class__.__name__} {self.breed} is getting sick.')
def die(self):
print(f'{__class__.__name__} {self.breed} is dying.')
def sell(self): # 自己的行為:交易
print(f'{__class__.__name__} {self.breed} is being sold.')
tree1 = Hardwood('oak', 50, 100)
tree1.breed
tree1.grow()
tree1.reproduce()
tree1.get_sick()
tree1.die()
tree1.defoliate()
tree1.bloom()
print()
tree2 = Conifer('cedar', 96, 1_500)
tree2.breed
tree2.grow()
tree2.reproduce()
tree2.get_sick()
tree2.die()
tree2.sell()
抽象類別的介紹到此為止。明天講繼承的最後一個主題,也可能是繼承的最後一篇。
註1: concrete methods當然也可譯為「具體方法」,筆者捨「具體」而取「實體」,個人喜好耳。