1. 程式人生 > 實用技巧 >一文了解python的 @property

一文了解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我們介紹完畢。

  我想說的是,既然使用的是面向物件的語言,那麼我們要善用面向物件中封裝這個特性,使它能夠發揮出重要的作用。