今天繼續property之旅。可能是最後一篇了,試用講故事形式。
alextree
的套件,其中有一個Tree
類別。pip install alextree
,以提升Alex & Son Co.公司聲譽。class Tree():
'''version 1.0'''
def __init__(self, breed: str, age: int, height: int):
self.breed = breed # 所有attributes全是public。
self.age = age # 存取最簡單,但也最缺保護。
self.height = height
def xx(self): # 略
... # Ellipsis
def yy(self): # 略
... # Ellipsis
def show_info(self):
print(f'{self.breed=:<10}{self.age=:<10,}{self.height=}')
while True
無窮迴圈。tree = Tree('cedar', 50, 37)
tree.show_info()
tree.breed = 123 # 將整數賦給樹種
tree.age = True # 將bool賦給樹齡
tree.height = -50 # 樹高為負
tree.show_info()
輸出:tree.age = True
,存進去的卻是1
(註1),而1的型和值都符合規定(1歲的樹是允許的呀),因而大幅增加檢核的複雜程度。class Tree():
'''version 2.0'''
def __init__(self, breed: str, age: int, height: int):
self.__breed = breed # 所有attributes全改為private。
self.__age = age
self.__height = height
def get_breed(self) -> str: # public getter for __breed
return self.__breed
def set_breed(self, breed: str): # public setter for __breed
if not isinstance(breed, str):
raise TypeError('樹種必須是字串。')
breeds = {'cedar': (0, 5_000), 'oak': (0, 300),
'beech': (0, 400), 'camphor': (0, 800),
'maple': (0, 500), 'phoebe': (0, 2_000)}
breed = breed.strip().lower()
if breed not in breeds:
raise Exception(f"樹種名稱'{breed}'不正確。")
min_age = breeds.get(breed)[0]
max_age = breeds.get(breed)[1]
if self.__age < min_age or self.__age > max_age:
raise Exception(f"新樹種名稱'{breed}'和其年齡不匹配。")
self.__breed = breed
def get_age(self) -> int: # public getter for __age
return self.__age
def set_age(self, age: int): # public setter for __age
if isinstance(age, bool) or not isinstance(age, int):
raise TypeError('樹齡必須是整數。')
# 以下的條件判斷只是「示意」,實際上該和breed一併考慮才對。
if age > 15_000 or age < 0:
raise Exception(f'樹齡數字{age}不合理。')
self.__age = age
def get_height(self) -> int: # public getter for __height
return self.__height
def set_height(self, height: int): # public setter for __height
if isinstance(height, bool) or not isinstance(height, int):
raise TypeError('樹高必須是整數。')
if height > 200 or height < 1:
raise Exception(f'樹高數字{height}不合理。')
self.__height = height
class Tree():
'''version 3.0'''
def __init__(self, breed: str, age: int, height: int):
self.__breed = breed # attributes依然全是private。
self.__age = age
self.__height = height
@property
def breed(self) -> str:
'''The breed property(getter).'''
return self.__breed
@breed.setter
def breed(self, breed: str):
'''The breed property(setter).'''
if not isinstance(breed, str):
raise TypeError('樹種必須是字串。')
breeds = {'cedar': (0, 5_000), 'oak': (0, 300),
'beech': (0, 400), 'camphor': (0, 800),
'maple': (0, 500), 'phoebe': (0, 2_000)}
breed = breed.strip().lower()
if breed not in breeds:
raise Exception(f"樹種名稱'{breed}'不正確。")
min_age = breeds.get(breed)[0]
max_age = breeds.get(breed)[1]
if self.__age < min_age or self.__age > max_age:
raise Exception(f"新樹種名稱'{breed}'和其年齡不匹配。")
self.__breed = breed
@property
def age(self) -> int:
'''The age property(getter).'''
return self.__age
@age.setter
def age(self, age: int):
'''The age property(setter).'''
if isinstance(age, bool) or not isinstance(age, int):
raise TypeError('樹齡必須是整數。')
# 以下的條件判斷只是「示意」,實際上該和breed一併考慮才對。
if age > 15_000 or age < 0:
raise Exception(f'樹齡數字{age}不合理。')
self.__age = age
@property
def height(self) -> int:
'''The height property(getter).'''
return self.__height
@height.setter
def height(self, height: int):
'''The height property(setter).'''
if isinstance(height, bool) or not isinstance(height, int):
raise TypeError('樹高必須是整數。')
if height > 200 or height < 1:
raise Exception(f'樹高數字{height}不合理。')
self.__height = height
def show_info(self):
print(f'{self.breed=:10}{self.age=:<10,}{self.height=:<10}')
測試-1。賦予不正確的樹種(tree.breed = 'aspen'
):
try:
tree = Tree('cedar', 2593, 37)
tree.show_info()
tree.breed = 'aspen' # 表面是一般的賦值給attribute,隱藏在背後的卻是property。
except Exception as e:
print(f'錯誤訊息:{str(e)}')
finally:
tree.show_info()
輸出:
測試-2。樹齡給None(tree.age = None
):
try:
tree = Tree('cedar', 2593, 37)
tree.show_info()
tree.age = None # 表面是一般的賦值給attribute,隱藏在背後的卻是property。
except Exception as e:
print(f'錯誤訊息:{str(e)}')
finally:
tree.show_info()
輸出:
測試-3。樹高為負數(tree.height = -50
):
try:
tree = Tree('cedar', 2593, 37)
tree.show_info()
tree.height = -50 # 表面是一般的賦值給attribute,隱藏在背後的卻是property。
except Exception as e:
print(f'錯誤訊息:{str(e)}')
finally:
tree.show_info()
輸出:
測試-4。樹種、樹齡和樹高均給合理的值:
try:
tree = Tree('cedar', 2593, 37)
tree.show_info()
tree.age = 2_000
tree.breed = ' Phoebe '
tree.height = 80
except Exception as e:
print(f'錯誤訊息:{str(e)}')
finally:
tree.show_info()
Bingo!
後記:最終alextree
套件成功發布至PyPI,因而打開公司知名度。周年慶時Spring榮獲特殊貢獻獎。老闆Alex龍顏大悅,加發紅利犒賞三軍。
Property的介紹到此結束,是否圓滿和正確周延則不得而知。最後給個總結:
註1: 欲存True
卻變成1
,原因是受到類別show_info()
方法中的f-string{self.age=:<10,}
影響。加了format specifier:<10,
後,原來的True
會自動轉為整數1
,False
則轉為0
。刪除此format specifier則可顯示True或False。問題是:類別的設計者原本預期age是整數而非布林。類別設計者加上format specifier並沒有錯而且是排版必需。可惜世上總有不按牌理出牌之人。