本文講解了 Python 的 property 特性,即一種符合 Python 哲學地設置 getter 和 setter 的方式。 ...
本文講解了 Python 的 property 特性,即一種符合 Python 哲學地設置 getter 和 setter 的方式。
Python 有一個概念叫做 property,它能讓你在 Python 的面向對象編程中輕鬆不少。在瞭解它之前,我們先看一下為什麼 property 會被提出。
一個簡單的例子
比如說你要創建一個溫度的類Celsius
,它能存儲攝氏度,也能轉換為華氏度。即:
class Celsius:
def __init__(self, temperature = 0):
self.temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
我們可以使用這個類:
>>> # 創建對象 man
>>> man = Celsius()
>>> # 設置溫度
>>> man.temperature = 37
>>> # 獲取溫度
>>> man.temperature
37
>>> # 獲取華氏度
>>> man.to_fahrenheit()
98.60000000000001
最後額外的小數部分是浮點誤差,屬於正常現象,你可以在 Python 里試一下
1.1 + 2.2
。
在 Python 里,當我們對一個對象的屬性進行賦值或估值時(如上面的temperature
),Python 實際上是在這個對象的 __dict__
字典里搜索這個屬性來操作。
>>> man.__dict__
{'temperature': 37}
因此,man.temperature
實際上被轉換成了man.__dict__['temperature']
。
假設我們這個類被程式員廣泛的應用了,他們在數以千計的客戶端代碼里使用了我們的類,你很高興。
突然有一天,有個人跑過來說,溫度不可能低於零下273度,這個類應該加上對溫度的限制。這個建議當然應該被採納。作為一名經驗豐富的程式員,你立刻想到應該使用 setter 和 getter 來限制溫度,於是你將代碼改成下麵這樣:
class Celsius:
def __init__(self, temperature = 0):
self.set_temperature(temperature)
def to_fahrenheit(self):
return (self.get_temperature() * 1.8) + 32
# 更新部分
def get_temperature(self):
return self._temperature
def set_temperature(self, value):
if value < -273:
raise ValueError("Temperature below -273 is not possible")
self._temperature = value
很自然地,你使用了“私有變數”_temperature
來存儲溫度,使用get_temperature()
和set_temperature()
提供了訪問_temperature
的介面,在這個過程中對溫度值進行條件判斷,防止它超過限制。這都很好。
問題是,這樣一來,使用你的類的程式員們需要把他們的代碼中無數個obj.temperature = val
改為obj.set_temperature(val)
,把obj.temperature
改為obj.get_temperature()
。這種重構實在令人頭痛。
所以,這種方法不是“向下相容”的,我們要另闢蹊徑。
@property 的威力!
想要使用 Python 哲學來解決這個問題,就使用 property。直接看代碼:
class Celsius:
def __init__(self, temperature = 0):
self.temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
def get_temperature(self):
print("Getting value")
return self._temperature
def set_temperature(self, value):
if value < -273:
raise ValueError("Temperature below -273 is not possible")
print("Setting value")
self._temperature = value
# 重點在這裡
temperature = property(get_temperature,set_temperature)
我們在class Celsius
的最後一行使用了一個 Python 內置函數(類) property()
。它接受兩個函數作為參數,一個 getter,一個 setter,並且返回一個 property 對象(這裡是temperature
)。
這樣以後,任何訪問temperature
的代碼都會自動轉而運行get_temperature()
,任何對temperature
賦值的代碼都會自動轉而運行set_temperature()
。我們在代碼裡加了print()
便於測試它們的運行狀態。
>>> c = Celsius() # 此時會運行 setter,因為 __init__ 里對 temperature 進行了賦值
Setting value
>>> c.temperature # 此時會運行 getter,因為對 temperature 進行了訪問
Getting value
0
需要註意的是,實際的溫度存儲在_temperature
里,temperature
只是提供一個訪問的介面。
深入瞭解 Property
正如之前提到的,property()
是 Python 的一個內置函數,同時它也是一個類。函數簽名為:
property(fget=None, fset=None, fdel=None, doc=None)
其中,fget
是一個 getter 函數,fset
是一個 setter 函數,fdel
是刪除該屬性的函數,doc
是一個字元串,用作註釋。函數返回一個 property 對象。
一個 property 對象有 getter()
、setter()
和deleter()
三個方法用來指定相應綁定的函數。之前的
temperature = property(get_temperature,set_temperature)
實際上等價於
# 創建一個空的 property 對象
temperature = property()
# 綁定 getter
temperature = temperature.getter(get_temperature)
# 綁定 setter
temperature = temperature.setter(set_temperature)
這兩個代碼塊等價。
熟悉 Python 裝飾器的程式員肯定已經想到,上面的 property 可以用裝飾器來實現。
通過裝飾器@property
,我們可以不定義沒有必要的 get_temperature()
和set_temperature()
,這樣還避免了污染命名空間。使用方式如下:
class Celsius:
def __init__(self, temperature = 0):
self._temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
# Getter 裝飾器
@property
def temperature(self):
print("Getting value")
return self._temperature
# Setter 裝飾器
@temperature.setter
def temperature(self, value):
if value < -273:
raise ValueError("Temperature below -273 is not possible")
print("Setting value")
self._temperature = value
你可以使用裝飾器,也可以使用之前的方法,完全看個人喜好。但使用裝飾器應該是更加 Pythonic 的方法吧。
參考
(本文完)