今天起介紹封裝的另一個主題:attributes vs properties(或單數attribute vs property)。
物件.屬性
的方式存取(註1),必須利用由類別提供的公開方法(getters和setters)。print(f'{tree_keeper=}')
、直接以=
賦值如tree_keeper = 'Audrey Hepburn'
。而自訂類別中的(私有)屬性,卻必須透過getters和setters方法取值賦值,存取方式明顯不如一般變數的簡單和直觀:
class Tree():
def __init__(self, breed: str, age: int, height: int):
self.__breed = breed # private attribute
self.__age = age # private attribute
self.__height = height # private attribute
def get_age(self) -> int: # public getter for age
return self.__age
def set_age(self, age: int): # public setter for age
age_ranges = {'camphor': [0, 800], 'oak': [0, 300]}
if age < age_ranges[self.__breed][0] or age > age_ranges[self.__breed][1]:
raise Exception('樹齡數字不合理。')
else: # 放行
self.__age = age
# 主程式
tree_keeper = 'Ingrid Bergman'
print(f'{tree_keeper=}') # 一般變數可以直接取值。
tree_keeper = 'Audrey Hepburn' # 一般變數可以直接賦值。
print(f'{tree_keeper=}')
tree = Tree('camphor', 50, 37)
print(f'{tree.get_age()=}') # 物件的私有屬性要動用getter取值。
tree.set_age(700) # 物件的私有屬性要動用setter賦值。
print(f'{tree.get_age()=}')
輸出:using System;
namespace MyApplication
{
class Tree
{
private string breed; // 這是private attribute。
public string Breed // 這是public property。
{
// 將getter和setter包在property裡面。
get { return breed; } // getter
// 透過public property修改private attribute。
set { breed = value; } // setter
}
}
class Program
{
static void Main(string[] args)
{
Tree tree = new Tree();
tree.Breed = "cedar"; // 直接賦值,表面上沒有使用getter。
Console.WriteLine(tree.Breed); // 直接取值,看不到setter影子。
}
}
}
輸出:class Tree:
def __init__(self, breed: str, age: int): # constructor建構子
self.__breed = breed # __breed和__age是private attribute。
self.__age = age # 有些人會錯用「一條」前綴底線來代表private。
def __get_breed(self) -> str: # private getter
return self.__breed
def __get_age(self) -> int: # private getter
return self.__age
def __set_age(self, age: int): # private setter
if age > 15000 or age < 0:
raise Exception('樹齡數字不合理。')
self.__age = age
def __del_age(self): # private deleter(較少用)
del self.__age
# 以下就是property了,請「畫重點」。
# fget, fset, fdel都是property()的關鍵字參數。顧名思義,不必解釋。
breed = property(fget=__get_breed) # breed和age是property。
age = property(fget=__get_age, fset=__set_age, fdel=__del_age)
主程式長這樣:
try:
tree = Tree('cedar', 150) # 建立物件時設定初值:雪松, 150歲。
print(f'{tree.breed=}') # 貌似attribute,實則property。
print(f'aa: {tree.age=}')
tree.age = -100 # 賦值(這裡會產生Exception)。
print(f'bb: {tree.age=}') # 這行沒有機會執行。
except Exception as e:
print(f'錯誤訊息:{str(e)}')
finally:
print(f'cc: {tree.age=}')
輸出如下:tree.breed
中的breed
,以及tree.age
中的age
,名稱前無前綴底線,看來是公開屬性(public attributes),其實都是properties。print(f'{tree.breed=}')
和tree.age = -100
的寫法,和存取普通變數形式一致,相當直觀。print(f'{tree.breed=}')
中的tree.breed
,骨子裡是呼叫類別內部的私有方法__get_breed()
。tree.age = -100
其實是呼叫類別內部的私有方法__set_age()
。執行這行時,-100超出設定的合理範圍,因而觸發Exception。所以print(f'bb: {tree.age=}')
這行不會執行,直接跳到finally區塊。註1: 假設不使用Day8「在外部修改私有屬性,真行嗎?」篇提到的方法。
註2: 語法糖(syntatic sugar)又稱語法甜頭(筆者個人較傾向此名)。是程式語言的一種機制,在和原本語法效果完全相同前提下,提供另一種比較簡短的語法。像C語言的i++
就可以算是一種語法甜頭,它和i = i + 1
等價。Python的i += 1
也是,只是沒有i++
那麼「甜」而已。