屬性函數(property)
在對象中兩個很重要的元素就是屬性和方法,在調用的時候兩者是有區別的。
class People:
def __init__(self,first_name,last_name):
self.first_name = first_name
self.last_name = last_name
def get_first_name(self):
return self.first_name
a = People('lala','ouyang')
print(a.get_first_name())
print(a.first_name)
從例子中我們可以發現,一樣的結果,但是調用的過程不一樣(雖然其實也就是多一個括號而已),那么有沒有一種辦法,使得我們調用屬性的時候就會自動調用相應的方法,也就是增加一些額外的處理過程(例如類型檢查或者驗證)。這時候屬性函數(@property)就能給我們提供很好的解決方案。
首先是最簡單的例子,自動調用get,set函數對屬性的處理。
class People:
def __init__(self,name):
self.name = name
#getter function
@property #屬性函數
def name(self):
return self._name
#setter function
@name.setter
def name(self,name):
self._name = name
a = People('leida')
print(a.name)
a.name = 'libai'
print(a.name)
正如例子中這樣。要定義對屬性的訪問,一種最簡單的方法就是將其定義為property。比如說,增加對屬性的類型檢查:
class People:
def __init__(self,name):
self.name = name
#getter function
@property #屬性函數
def name(self):
return self._name
#setter function
@name.setter
def name(self,name):
if not isinstance(name,str):
raise TypeError('name must is string type')
self._name = name
a = People(12)
當我們實現一個property時,底層數據仍然需要保存在某個地方,因而在get和set的方法中,可以看到直接對_name操作的,這就是數據實際保存的地方。但是,也發現在init()方法中任然是對self.name操作的。但是實際情況是我初始化的時候程序仍舊是對self._name操作的。(這點我也還不理解,應該不是這樣的啊.但是必須這么寫,不然會報錯)。
對于已經存在的get,set方法,同樣也可以定義為property:
class People:
def __init__(self,name):
self.name = name
def get_name(self):
print('calling the get function')
return self._name
def set_name(self,name):
print('calling the set function')
self._name = name
name = property(get_name,set_name,del_name)
a = People('libai')
同時,在set_name 函數中做了打印標記,發現在init()方法中確實調用了set_name()函數。
Property屬性實際上就是把一系列的方法綁定到一起。如果檢查類的property屬性,就會發現property自身所持有的屬性fget,fset所代表的原始方法。
print(People.name.fget)
print(People.name.fset)
一般來說,我們不會直接去調用fset或者fget,但是當我們調用property屬性時會自動觸發對這些方法的調用。
上面例子中的兩種寫法,一般傾向于第二種寫法,特別是如果需要對某個普通的屬性額外增加處理步驟時,可以在不修改已有代碼的情況下將這個屬性提升為一個property。
Property也可以用來定義需要計算的屬性,這類屬性并不會實際被保存起來,而是根據需要完成計算。
import math
class Circle:
def __init__(self,radius):
self.radius = radius
@property
def area(self):
return math.pi * self.radius**2
@property
def perimeter(self):
return 2*math.pi*self.radius
c = Circle(5)
print(c.area)
print(c.perimeter)
這樣的寫法就會使得實例的接口變得統一,本來用方法實現的計算調用的時候用屬性就可以,很好的避免了方法、屬性傻傻分不清的情況了。
個人建議,不要在代碼中不斷重復使用@property,這樣會使得代碼變得臃腫,而且難以閱讀,容易出錯。同樣的任務,利用描述符或者閉包也能夠很好的完成。