2020年3月8日 星期日

Python @ Property 修飾符 初學者速理解

初學者第一次看到「@」,像我,就滿頭問號。

網路上教學很多,而且不難懂,只是沒有我的風格。(推薦: 官方文件)

@又名Property屬性,property() 是python的內置函數。在定義 類別class 的時候,也經常設定其相關屬性。

Property由四個部分組成:getter setterdeleter、 doc。
以a為例,getter、 setter、 deleter分別對應:
a # call getter
a = 1 # call setter
del a # call deleter
發生以上三種狀況,就會分別執行對應的函式。

依照官方的範例稍微改寫,例1:
class C(object):
    def __init__(self):
        self._x = None
    def getx(self):
        print("得了")
        return self._x
    def setx(self, value):
        print("設了")
        self._x = value
    def delx(self):
        print("刪了")
        del self._x
    x = property(getx, setx, delx, "docstring")

a = C()
a.x # a.getx()
a.x = 1 # a.setx()
del a.x # a.delx()
得到以下輸出。
得了
設了
刪了
使用Property後可以減少代碼,除了例1的寫法,Property可以寫得更簡化。
用上「@」,更快速使用Property,簡化代碼。例1也可以寫成例2:
class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        print("得了")
        return self._x

    @x.setter
    def x(self, value):
        print("設了")
        self._x = value

    @x.deleter
    def x(self):
        print("刪了")
        del self._x
@property 就是將下方函式定義為 x 的 getter, 然後 x = property(getter),下方函式名 x 就會變成屬性。
看例2,@property 為 getter。 setter也可以寫成@屬性.setter 、 deleter 寫成@屬性.deleter。
getter 最常被使用也一定要設置,setter、 deleter  就不一定要設置。

知道使用 @property 可以將 a.getx() 省略成 a.x 。a.x 就等於 a.getx() 的回傳值。

舉一個實例,如果要定義一個長方形類別,可能會在 __init__() 中,設定其長與寬,然後透過長與寬計算出面積。
class Rectangle():
    def __init__(self, width, height):
        self.width = width
        self.height = height
        self.area = width * height
如果 width = 5、 height = 4,那麼 area = 20。

但是,要是後面只改變了 width 或是 height, area 並不會自動更新數值,仍是舊的 20。
如果用上 @property 設定 area,就會在呼叫 area 時,重新計算數值。
class Rectangle():
    def __init__(self, width, height):
        self.width = width
        self.height = height

    @property
    def area(self):
        return self.width * self.height
使用  @property 可將像 area 這類動態數值的過程簡化成屬性名,除了大幅減少代碼篇幅,使用上也變得更直覺,。

學到這裡,講完了property的基本運作,現在來看一下其他教學中的舉例。
如果有好好從前面讀懂,這個範例應該是很好理解,範例3:
def f1(f2):
    return 1

@f1
def f2(x):
    return 2
 f2(1) # Error 
先不講結果,先順著 property 的邏輯看。
@f1,getter 為 f1,f2 = property(f1),下方函式名 f2 變成屬性。
於是 f2 == 「f1的回傳值」 == 1、f2 + f2 == 2。
行7嘗試 f2(1) ,就會報錯,無法理解 f2(1) 為什麼會錯, 就換成 1(1) 理解看看吧。

行1寫成 f1(f2),是方便理解整個 f2函式 都會傳入到 f1。
由於函式內外部差異,可以將傳入的 f2 跟外部的 f2 視作不同的東西。
因此呼叫傳入的 f2 並不會觸發外部帶有@的 f2。

最後範例,如果無法理解,就回過頭看上方的說明吧。範例4:
def addition(f):
    answer = 0
    for num in f():
        answer += num
    return answer

@addition
def threePlusFive():
    return [3, 5]

@addition
def threePlusFivePlusEight():
    return [3, 5, 8]

@addition
def threePlusFivePlusEight2():
    return [threePlusFive, 8]

print(threePlusFive)
print(threePlusFivePlusEight)
print(threePlusFivePlusEight2)