python3_內建函式__property詳細講解
Python中有個很讚的概念,叫做property,它使得面向物件的程式設計更加簡單。在詳細解釋和深入瞭解Python中的property之前,讓我們首先建立這樣一個直覺:為什麼我們需要用到property?
1.例項
建立一個類,用來存放攝氏溫度。當然這個類也需要實現一個將攝氏溫度轉換為華氏溫度的方法,我們可以用這個類產生一個物件,然後按照我們期望的方式改變該物件的溫度屬性。一種實現的方式如下:
class Celsius: def __init__(self, temperature = 0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 if "__main__" == __name__: man = Celsius() man.temperature = 37 print(man.temperature) print(man.to_fahrenheit())
每當賦值或取任何物件的屬性時,例如上面展示的溫度,Python都會從物件的__dict__
字典(內部不包括私有屬性)中搜索。因此,man.temperature在其內部就變成了man.__dict__['temperature']
print(man.__dict__) # {'temperature': 37}
現在,讓我們進一步假設我們的類在客戶中很受歡迎,他們開始在其程式中使用這個類。他們對該類生成的物件做了各種操作。有一天,一個受信任的客戶來找我們,建議溫度不能低於-273攝氏度(熱力學的同學可能會提出異議,它實際上是-273.15),也被稱為絕對零。客戶進一步要求我們實現這個值約束
2.使用Getters和Setters
對於上邊的約束,一個很容易想到的解決方案是隱藏其溫度屬性(使其私有化),並且定義新的用於操作溫度屬性的getter和setter介面。可以如下方式實現。但這樣並不會讓人很放心。上述更新的最大問題是,所有在他們的程式中使用了我們先前類的客戶都必須更改他們的程式碼:obj.temperature改為obj.get_temperature(),所有的賦值語句也必須更改,比如obj.temperature = val改為obj.set_temperature(val)。這樣的重構會給那些擁有成千上萬行程式碼的客戶帶來很大的麻煩
class Celsius:
def __init__(self, temperature = 0):
self.set_temperature(temperature)
def to_fahrenheit(self):
return (self.get_temperature() * 1.8) + 32
# new update
def get_temperature(self):
return self._temperature
def set_temperature(self, value):
if value < -273:
raise ValueError("Temperature below -273 is not possible")
# _temperature為類的私有屬性(精確意義上來說python有私有屬性)
self._temperature = value
if "__main__" == __name__:
# c = Celsius(-277) # error
c = Celsius(37)
c.get_temperature()
c.set_temperature(10)
# c.set_temperature(-300) # error
3.property的作用
對於上邊的問題,Python式的解決方式是使用property。這裡是我們已經實現了的一個版本(如下部程式碼)。類程式碼的最後一行,建立了一個property物件temperature。簡單地說,property將一些程式碼(get_temperature
和set_temperature
)附加到成員屬性(temperature)的訪問入口。任何獲取temperature值的程式碼都會自動呼叫get_temperature()
,而不是去字典表(__dict__
)中進行查詢。同樣的,任何賦給temperature值的程式碼也會自動呼叫set_temperature()
。這是Python中一個很酷的功能。
class Celsius:
def __init__(self, temperature = 0):
print("init value")
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物件,為使用者提供外部介面
temperature = property(get_temperature,set_temperature)
if "__main__" == __name__:
c = Celsius() # init value / Setting value
print(c.temperature) # Getting value
c.temperature = 37 # Setting value
c.to_fahrenheit() # Getting value
我們可以看到,通過使用property,我們在不需要客戶程式碼做任何修改的情況下,修改了我們的類,並實現了值約束。因此我們的實現是向後相容的,這樣的結果,大家都很高興。最後需要注意的是,實際溫度值儲存在私有變數_temperature
中。屬性temperature是一個property物件,是用來為這個私有變數提供介面的。
4.深入挖掘property
在Python中,property()是一個內建函式,用於建立和返回一個property物件。該函式的簽名為:
property(fget=None, fset=None, fdel=None, doc=None)
這裡,fget是一個獲取屬性值的函式,fset是一個設定屬性值的函式,fdel是一個刪除屬性的函式,doc是一個字串(類似於註釋)。從函式實現上看,這些函式引數都是可選的。
Property物件有三個方法,getter(), setter()和delete(),用來在物件建立後設置fget,fset和fdel。這就意味著,這行程式碼:temperature = property(get_temperature,set_temperature)可以被分解為:
# 建立空的property物件
temperature = property()
# 設定fget
temperature = temperature.getter(get_temperature)
# 設定fset
temperature = temperature.setter(set_temperature)
熟悉Python中裝飾器(decorator)的程式設計師能夠認識到上述結構可以作為decorator實現。我們可以更進一步,不去定義名字get_temperature和set_temperature,因為他們不是必須的,並且汙染類的名稱空間。為此,我們在定義getter函式和setter函式時重用名字temperature。下邊的程式碼展示如何實現它。
class Celsius:
def __init__(self, temperature = 0):
self._temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
@property
def temperature(self):
print("Getting value")
return self._temperature
@temperature.setter
def temperature(self, value):
if value < -273:
raise ValueError("Temperature below -273 is not possible")
print("Setting value")
self._temperature = value
@temperature.deleter
def temperature(self):
print("Deleter value")
del self._temperature