一文了解python的 @property
參考自: https://www.programiz.com/python-programming/property
Python為我們提供了一個內建裝飾器@property,此方法使得getter和setter在面向物件程式設計中使用更加容易。
在細說@property之前,我們先舉一個簡單的例子,讓大家瞭解為什麼需要它。
Class Without Getters and Setters
假設我們需要一個類用來儲存溫度的攝氏度。這個類不但提供攝氏度,還提供一個攝氏度轉華氏度的方法。該類如下所示:
class Celsius: def __init__(self, temperature = 0): self.temperature= temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32
接下來展示我們怎麼使用這個類。
# 溫度類
class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 # 建立物件 human = Celsius()# 設定溫度 human.temperature = 37 # 獲得溫度 print(human.temperature) # 獲取華氏度 print(human.to_fahrenheit())
輸出。
37 98.60000000000001
轉換為華氏溫度時,小數點後的額外位置是由於浮點運算錯誤造成的。
當我們在設定和獲取任何物件的屬性時(如上文所示的tempurature)時,Python都會在物件的內建dict字典屬性中搜索它。
>>> human.__dict__ {'temperature': 37}
所以我們還可以這樣做。
>>> human.__dict__['temperature'] 37
Using Getters and Setters
如果我們想擴充套件上面定義的Celsius類。我們都知道任何物體的溫度都不能低於-273.15攝氏度(熱力學中的絕對零度)。
讓我們豐富這個類讓它來能夠約束tempurature這個值。
約束tempurature的方法就是將tempurature設定為私有屬性,並定義setter和getter方法來操作它。
# 添加了Getters和Setter方法 class Celsius: def __init__(self, temperature=0): self.set_temperature(temperature) def to_fahrenheit(self): return (self.get_temperature() * 1.8) + 32 # getter def get_temperature(self): return self._temperature # setter def set_temperature(self, value): if value < -273.15: raise ValueError("Temperature below -273.15 is not possible.") self._temperature = value
在Celsius類中,temperature變成_temperature。開頭加下劃線是用來表示私有變數(約定俗成的規定)。
接著讓我們看看怎麼使用這個類。
class Celsius: def __init__(self, temperature=0): self.set_temperature(temperature) def to_fahrenheit(self): return (self.get_temperature() * 1.8) + 32 # getter def get_temperature(self): return self._temperature # setter def set_temperature(self, value): if value < -273.15: raise ValueError("Temperature below -273.15 is not possible.") self._temperature = value # 建立溫度類 human = Celsius(37) # 通過getter方法獲取溫度 print(human.get_temperature()) print(human.to_fahrenheit()) # setter方法設定溫度 human.set_temperature(-300) print(human.to_fahrenheit())
輸出。
37 98.60000000000001 Traceback (most recent call last): File "<string>", line 30, in <module> File "<string>", line 16, in set_temperature ValueError: Temperature below -273.15 is not possible.
這個示例成功演示了溫度類不允許把溫度設定在-273.15度以下。
在Python中temperature屬性依然可以通過類直接訪問,與Java不同,Python中並沒有private關鍵字。所以說在Python中,變數前新增下劃線變成私有變數也就是約定俗稱的規定,大家都應遵守。
>>> human._temperature = -300 # 依然可以更改此屬性 >>> human.get_temperature() -300
然而,上面的類帶來了一個新的問題,我們再也不能通過obj.temperature = obj 和 value = obj.emperature,取而代之的是obj.set_temperature(obj)和value = obj.get_temperature()。
此時,該Python的內建裝飾器出場了。
The property Class
處理上述問題的pythonic方法是使用property類。下面是我們如何更新程式碼:
# 使用property的類 class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 # getter def get_temperature(self): print("Getting value...") return self._temperature # setter def set_temperature(self, value): print("Setting value...") if value < -273.15: raise ValueError("Temperature below -273.15 is not possible") self._temperature = value # 建立一個property物件 temperature = property(get_temperature, set_temperature)
我們添加了一個print()方法在get_temperature和set_temperature方法中。
類中的最後一行,我們建立了一個property物件--temperature。讓我們看一下怎麼使用這個新類。
# using property class class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 # getter def get_temperature(self): print("Getting value...") return self._temperature # setter def set_temperature(self, value): print("Setting value...") if value < -273.15: raise ValueError("Temperature below -273.15 is not possible") self._temperature = value # creating a property object temperature = property(get_temperature, set_temperature) human = Celsius(37) print(human.temperature) print(human.to_fahrenheit()) human.temperature = -300
輸出。
Setting value... Getting value... 37 Getting value... 98.60000000000001 Setting value... Traceback (most recent call last): File "<string>", line 31, in <module> File "<string>", line 18, in set_temperature ValueError: Temperature below -273 is not possible
上面我們看到了,當對temperature進行操作時,會自動呼叫到get_temperature和set_temperature方法。前面我們也提到,當通過Celsius的物件獲取temperature屬性時會從obj.__dict__中找到temperature屬性進行操作。而當前這個類並沒有這樣。
當我們在溫度類中對temperature進行賦值時也會呼叫到set_temperature方法。
>>> human = Celsius(37)
Setting value...
再看看下面對temperature的使用。
>>> human.temperature Getting value 37 >>> human.temperature = 37 Setting value >>> c.to_fahrenheit() Getting value 98.60000000000001
The @property Decorator
在Python中,property()是內建方法。此方法的定義如下。
property(fget=None, fset=None, fdel=None, doc=None)
引數解釋:
- fget:獲取屬性值的方法。
- fset:設定屬性值的方法。
- fdel:刪除屬性值的方法。
- doc:字串(類似於說明)。
從實現中可以看出,這些函式引數是可選的。因此,屬性物件可以簡單地建立如下。
>>> property()
<property object at 0x0000000003239B38>
在Celsius類中我們添加了如下程式碼。
temperature = property(get_temperature,set_temperature)
當然我們也可以這樣做。
# make empty property temperature = property() # assign fget temperature = temperature.getter(get_temperature) # assign fset temperature = temperature.setter(set_temperature)
熟悉Python裝飾器的程式設計師可以認識到上面的構造可以作為裝飾器來實現。
接下來讓我們看一下如何運用裝飾器來替代上面的set_temperature和get_temperature。
# Using @property decorator 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): print("Setting value...") if value < -273.15: raise ValueError("Temperature below -273 is not possible") self._temperature = value # create an object human = Celsius(37) print(human.temperature) print(human.to_fahrenheit()) coldest_thing = Celsius(-300)
輸出。
Setting value... Getting value... 37 Getting value... 98.60000000000001 Setting value... Traceback (most recent call last): File "<string>", line 29, in <module> File "<string>", line 4, in __init__ File "<string>", line 18, in temperature ValueError: Temperature below -273 is not possible
到此,property我們介紹完畢。
我想說的是,既然使用的是面向物件的語言,那麼我們要善用面向物件中封裝這個特性,使它能夠發揮出重要的作用。