Getter 和 setter在java中被廣泛使用。一個好的java編程准則為:將所有屬性設置為私有的,同時為屬性寫getter和setter函數以供外部使用。 這樣做的好處是屬性的具體實現被隱藏,當未來需要修改時,只需要修改getter 和 setter即可,而不用修改代碼中所有引用這個屬性的地方。可能做的修改為:
- 在獲取或設置屬性時打一條日志
- 設置屬性時,對值對進檢查
- 設置發生時, 修改設置的值
- 獲取屬性時,動態地計算值
可謂是好處多多,getter和setter為變量訪問提供了靈活的方式。
但python中情況卻不同,因為對象屬性訪問的機制不同。java中需要為變量寫getter和setter的原因為:當我們寫這樣的表達式 person.name
來獲取一個 person
對象的 name
屬性時,這個表達式的意義是固定的,它就是獲取這個屬性,而不可能觸發一個函數的調用。但對於python, 這個表達式即可能是直接獲取一個屬性,也可能會調用一個函數。這取決 Person
類的實現方式。也就是說,python的對象屬性訪問的語法,天然就提供了getter和setter的功能。
由於這個區別,我們沒有必要在python中為每個對象的屬性寫getter和setter。最開始時,我們總是將屬性作為一個直接可訪問的屬性。當后續需要對這個屬性的訪問進行一些控制時,我們可以將其修改為函數觸發式屬性。在修改前后,調用這個對象屬性的代碼不用修改,因為還是使用相同的語法來訪問這個屬性。
可以使用@property 裝飾器將一個直接訪問的屬性轉變為函數觸發式屬性。如下所示,使用@property前的代碼為
class Person: def __init__(self, name): self.name = name person = Person("Tom") print(person.name)
代碼的輸出為:
Tom
此時為直接訪問 name
這個屬性。當我們需要確保 name
是一個字符串時,可以使用 @property 裝飾器將屬性轉變為一個函數調用,如下所示。
class Person: def __init__(self, name): self.name = name @property def name(self): print("get name called") return self._name @name.setter def name(self, name): print("set name called") if not isinstance(name, str): raise TypeError("Expected a string") self._name = name person = Person("Tom") print(person.name)
代碼的輸出為:
set name called
get name called
Tom
可以看出
- 在創建Person對象時(代碼的倒數第二行), 用於set name的函數被調用。這個函數會檢查輸入是否為一個字符串,如不是則raise一個TypeError
- 在獲取屬性時(代碼的最后一行),用於get name的函數被調用
- 在修改前后,使用Person類的代碼完全相同
總結
Python中對象訪問的語法即可能是直接訪問這個屬性,也可能是調用一個函數,這取決於類的實現方式。我們可以在不修改調用者代碼的前提下,輕松切換這兩種方式。可見python原生就提供了添加額外getter和setter所帶來的好處。因此沒有必要一開始就為對象屬性編寫getter和setter函數,而是在需要時切換到函數調用式屬性。