Python的封裝介紹差不多「開到荼靡」了。今天起分享的也許是最後一個大主題(註1)。
在正式介紹前,各位請沿用以上Tree類別,設想一下這個需求:
利用之前講過的知識和技術,上述兩個「簡單」問題,處理起來還真有點不簡單。問題在於:每棵樹都是獨立個體,樹與樹之間好像並無甚麼關連可供計算。
難道要像以下的code,竟然出動到一些外部變數去記錄和計算樹的數目和平均樹齡?唔,這顯然不是個好辦法(註2)。
class Tree():
def __init__(self, breed: str, age: int):
self.__breed = breed
self.__age = age
@property
def breed(self) -> str:
'''The breed property(getter).'''
return self.__breed
@property
def age(self) -> int:
'''The age property(getter).'''
return self.__age
def show_count_and_average(count: int, total_age: int):
print(f'{count=:<10,}{total_age=:<10,}average={total_age / count:,.2f}')
tree_count = 0 # 利用類別外部的變數記錄樹的數目。
total_tree_age = 0 # 記錄總樹齡。
tree1 = Tree('Cedar', 1_520)
tree_count += 1
total_tree_age += tree1.age
show_count_and_average(tree_count, total_tree_age)
tree2 = Tree('oak', 357)
tree_count += 1
total_tree_age += tree2.age
show_count_and_average(tree_count, total_tree_age)
tree3 = Tree('phoebe', 1806)
tree_count += 1
total_tree_age += tree3.age
show_count_and_average(tree_count, total_tree_age)
輸出:
也許您會說:不是有人說過一句名言「不管黑貓白貓,捉到老鼠就是好貓」嗎?需求能夠達成就好。
class Tree():
count = 0 # 放在constructor外面的是class attributes。
total_age = 0
average_age = 0
def __init__(self, breed: str, age: int):
self.__breed = breed
self.__age = age
Tree.count += 1 # class attributes是用Tree.xxx而非self.xxx
Tree.total_age += self.age
Tree.average_age = round(Tree.total_age / Tree.count, 2)
@property
def breed(self) -> str:
'''The breed property(getter).'''
return self.__breed
@property
def age(self) -> int:
'''The age property(getter).'''
return self.__age
def show_count_and_average(count: int, total_age: int):
print(f'{count=:<10,}{total_age=:<10,}average={total_age / count:,.2f}')
tree1 = Tree('Cedar', 1_520)
# Tree.count和tree1.count指向的是同一塊memory。這裡筆者故意寫作Tree.count / tree1.total_age,其實Tree.count / Tree.total_age,tree1.count / Tree.total_age,或者tree1.count / tree1.total_age都可以。當然最理想的表示式應該是Tree.count / Tree.total_age,明確指出count和total_age這兩個是class level的attributes。
show_count_and_average(Tree.count, tree1.total_age)
tree2 = Tree('oak', 357)
show_count_and_average(tree2.count, Tree.total_age)
tree3 = Tree('phoebe', 1806)
show_count_and_average(tree1.count, tree1.total_age)
以上這個「類別屬性」版,輸出和「外部變數版」完全相同:class Tree():
count = 0 # 放在constructor外面(前置)的是class attributes。
total_age = 0
average_age = 0
def __init__(self, breed: str, age: int): # constructor
...
或:
class Tree():
def __init__(self, breed: str, age: int): # constructor
...
count = 0 # 放在constructor外面(後置)的是class attributes。
total_age = 0
average_age = 0
註1: 「封裝」這個主題,真要慢慢磨還有些小題目可以講,不過太過支節的東西並不適合納入本系列。先預告:在封裝的最後一講筆者真的要講個很支節的小題目,輕鬆一下。
註2: 為了聚焦於今天的主題,前兩天介紹的dataclass decorator暫不使用,以傳統的方法來設計類別。
註3: 我發覺講「實例」有點不自然,還是回歸「物件」好了。