轉載:http://python.jobbole.com/81967/python
2.x版本須繼承object,才能實現。程序員
Python中有個很讚的概念,叫作property,它使得面向對象的編程更加簡單。在詳細解釋和深刻了解Python中的property以前,讓咱們首先創建這樣一個直覺:爲何咱們須要用到property?編程
假設有天你決定建立一個類,用來存儲攝氏溫度。固然這個類也須要實現一個將攝氏溫度轉換爲華氏溫度的方法。一種實現的方式以下:函數
Pythoncode
1對象 2繼承 3接口 4字符串 5get |
class Celsius: def __init__(self, temperature = 0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 |
咱們能夠用這個類產生一個對象,而後按照咱們指望的方式改變該對象的溫度屬性:
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 |
>>> # create new object >>> man = Celsius()
>>> # set temperature >>> man.temperature = 37
>>> # get temperature >>> man.temperature 37
>>> # get degrees Fahrenheit >>> man.to_fahrenheit() 98.60000000000001 |
這裏額外的小數部分是轉換成華氏溫度時因爲浮點運算偏差形成的(你能夠在Python解釋器中試試1.1 + 2.2)。每當咱們賦值或獲取任何對象的屬性時,例如上面展現的溫度,Python都會從對象的__dict__
字典中搜索它。
Python
1 2 |
>>> man.__dict__ {'temperature': 37} |
所以,man.temperature在其內部就變成了man.__dict__['temperature']
如今,讓咱們進一步假設咱們的類在客戶中很受歡迎,他們開始在其程序中使用這個類。他們對該類生成的對象作了各類操做。有一天,一個受信任的客戶來找咱們,建議溫度不能低於-273攝氏度(熱力學的同窗可能會提出異議,它其實是-273.15),也被稱爲絕對零。客戶進一步要求咱們實現這個值約束。做爲一個以爭取客戶滿意度爲己任的公司,咱們很高興地遵從了建議,發佈了1.01版本,升級了咱們現有的類。
對於上邊的約束,一個很容易想到的解決方案是隱藏其溫度屬性(使其私有化),而且定義新的用於操做溫度屬性的getter和setter接口。能夠這麼實現:
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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") self._temperature = value |
從上邊能夠看出,咱們定義了兩個新方法get_temperature()
和set_temperature()
,此外屬性temperature也被替換爲了_temperature
。最前邊的下劃線(_)用於指示Python中的私有變量。
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
>>> c = Celsius(-277) Traceback (most recent call last): ... ValueError: Temperature below -273 is not possible
>>> c = Celsius(37) >>> c.get_temperature() 37 >>> c.set_temperature(10)
>>> c.set_temperature(-300) Traceback (most recent call last): ... ValueError: Temperature below -273 is not possible |
這個更新成功地實現了新約束,咱們再也不容許設置溫度低於-273度。
請注意,Python中其實是沒有私有變量的。有一些簡單的被遵循的規範。Python自己不會應用任何限制。
Python
1 2 3 |
>>> c._temperature = -300 >>> c.get_temperature() -300 |
但這樣並不會讓人很放心。上述更新的最大問題是,全部在他們的程序中使用了咱們先前類的客戶都必須更改他們的代碼:obj.temperature改成obj.get_temperature(),全部的賦值語句也必須更改,好比obj.temperature = val改成obj.set_temperature(val)。這樣的重構會給那些擁有成千上萬行代碼的客戶帶來很大的麻煩。
總而言之,咱們的更新是不向後兼容地。這就是須要property閃亮登場的地方。
對於上邊的問題,Python式的解決方式是使用property。這裏是咱們已經實現了的一個版本:
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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) |
咱們在get_temperature()
和set_temperature()
的內部增長了一個print()函數,用來清楚地觀察它們是否正在執行。代碼的最後一行,建立了一個property對象temperature。簡單地說,property將一些代碼(get_temperature
和set_temperature
)附加到成員屬性(temperature)的訪問入口。任何獲取temperature值的代碼都會自動調用get_temperature()
,而不是去字典表(__dict__
)中進行查找。一樣的,任何賦給temperature值的代碼也會自動調用set_temperature()
。這是Python中一個很酷的功能。咱們實際演示一下。
Python
1 2 |
>>> c = Celsius() Setting value |
從上邊的代碼中咱們能夠看到,即便當咱們建立一個對象時,set_temperature()
也會被調用。你能猜到爲何嗎?緣由是,當一個對象被建立時,__init__()
方法被調用。該方法有一行代碼self.temperature = temperature。這個任務會自動調用set_temperature()
方法。
Python
1 2 3 |
>>> c.temperature Getting value 0 |
一樣的,對於屬性的任何訪問,例如c.temperature,也會自動調用get_temperature()
方法。這就是property所做的事情。這裏有一些額外的實例。
Python
1 2 3 4 5 6 |
>>> c.temperature = 37 Setting value
>>> c.to_fahrenheit() Getting value 98.60000000000001 |
咱們能夠看到,經過使用property,咱們在不須要客戶代碼作任何修改的狀況下,修改了咱們的類,並實現了值約束。所以咱們的實現是向後兼容的,這樣的結果,你們都很高興。
最後須要注意的是,實際溫度值存儲在私有變量_temperature
中。屬性temperature是一個property對象,是用來爲這個私有變量提供接口的。
在Python中,property()是一個內置函數,用於建立和返回一個property對象。該函數的簽名爲:
Python
1 |
property(fget=None, fset=None, fdel=None, doc=None) |
這裏,fget是一個獲取屬性值的函數,fset是一個設置屬性值的函數,fdel是一個刪除屬性的函數,doc是一個字符串(相似於註釋)。從函數實現上看,這些函數參數都是可選的。因此,能夠按照以下的方式簡單的建立一個property對象。
Python
1 2 |
>>> property() <property object at 0x0000000003239B38> |
Property對象有三個方法,getter(), setter()和delete(),用來在對象建立後設置fget,fset和fdel。這就意味着,這行代碼:temperature = property(get_temperature,set_temperature)能夠被分解爲:
Python
1 2 3 4 5 6 |
# make empty property temperature = property() # assign fget temperature = temperature.getter(get_temperature) # assign fset temperature = temperature.setter(set_temperature) |
它們之間是相互等價的。
熟悉Python中裝飾器decorator的程序員可以認識到上述結構能夠做爲decorator實現。咱們能夠更進一步,不去定義名字get_temperature和set_temperature,由於他們不是必須的,而且污染類的命名空間。爲此,咱們在定義getter函數和setter函數時重用名字temperature。下邊的代碼展現如何實現它。
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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 |
上邊的兩種生成property的實現方式,都很簡單,推薦使用。在Python尋找property時,你極可能會遇到這種相似的代碼結構。