[爆卦]python裝飾器使用時機是什麼?優點缺點精華區懶人包

為什麼這篇python裝飾器使用時機鄉民發文收入到精華區:因為在python裝飾器使用時機這個討論話題中,有許多相關的文章在討論,這篇最有參考價值!作者uranusjr (←這人是超級笨蛋)看板Python標題Re: [問題] 初學@propert...


※ 引述《ar0n77777 (property)》之銘言:
: 各位前輩好 新年快樂
: 這是關於《精通python》上的疑問
: 想請問 class 中 @property 的使用
: @property 這個 decorator 是在其他地方已經被定義了嗎
: 不能理解為何可以直接使用這個語法糖

是的


: 還有@property和@name.setter擺放位置的意義
: https://i.imgur.com/fGr96ny.jpg
: 另外就是不能理解property的實際功效和用途
: 麻煩各位了... 非常感謝!!

直接看 decorator 會很像 magic, 我們慢慢來
首先考慮這樣一個 class

class Duck:
def __init__(self, name):
self.name = name

這沒什麼好解釋的, 你可以很方便操作這個 attribute

>>> duck = Duck('Donald')
>>> duck.name
'Donald'
>>> duck.name = 'Daffy'
>>> duck.name
'Daffy'

但過了一陣子, 可能你需要在修改鴨子名字時, 同時做某些其他事情
例如確認名字一定是字串, 而且不能超過 100 字之類的
所以你就把 class 改寫成這樣

class Duck:
def __init__(self, name):
self._name = name

def get_name(self):
return self._name

def set_name(self, name):
if not isinstance(name, str) or len(name) > 100:
raise ValueError(name)
self._name = name

然後叫大家用 get_name() 與 set_name(), 而不要用本來的 name
但是 1. 一定會有忘記的時候, 2. 原本有的程式都要重寫很麻煩
所以這裡就是 property 出場的時機

property 是一個內建的 Python 型別 (和 list set 等等類似)
但這個型別特別的地方是應用了一個叫 descriptor 的概念
要從實作細節講起會花一千字以上, 所以這裡就直接看範例與最後結果

class Duck:
def __init__(self, name):
self._name = name

def get_name(self):
return self._name

def set_name(self, name):
if not isinstance(name, str) or len(name) > 100:
raise ValueError(name)
self._name = name

name = property(get_name, set_name)

在 Duck 上宣告叫 name 的 property, 然後把 get_name 與 set_name 傳給它
這個 property 在 Python 裡有四種被使用的方法

>>> duck.name # 取值
>>> duck.name = ... # 賦值
>>> del duck.name # 刪值
>>> help(duck.name) # 看文件

如果 duck.name 是一個正常普通的值 (例如最開始的那個版本)
上面動作會發生的事情很直觀, 你應該也知道
但是如果 name 是一個 descriptor, 則 Python 會去觸發它上面的對應 method
取值觸發 __get__, 賦值觸發 __set__, 其餘類推

所以當你取值時, 會發生這樣的事情:

1. 你呼叫 duck.name
2. Python 呼叫 duck.name.__get__() -> 會回傳一個值
3. Python 把這個值當作 duck.name 的回傳值, 把它送給你

賦值則是這樣:

1. 你把 'Duffy' 賦給 duck.name
2. Python 不會取代 duck.name, 而是呼叫 duck.name.__set__('Duffy')

回到 property, 這個 class 的實作大致包含下面這樣:

class property:
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
# 後面都一樣所以略過

def __get__(self, obj):
return self.fget(obj)

# 其他實作差不多, 略過

所以當你呼叫 duck.name 時, 大致會發生這樣的事情

1. duck.name
2. duck.name.__get__()
3. Duck.name.__get__(duck) [#1]
4. Duck.name.fget(duck)
5. Duck.get_name(duck) 因為我們把 get_name 傳進去了
6. 得到結果

[#1]: 剛好前幾篇才提到, 呼叫 instance.method() 等於 Class.method(instance)
這邊的狀況類似 (有微妙的不同), Python 會自動轉換呼叫的格式

注意 5. 等同於 duck.get_name() 所以結果就等於呼叫 property 的第一參數


以上就是 property 的簡單原理
但是這樣寫起來還是有點麻煩, 而且更重要的是, 不直觀
當你想知道 duck.name 時, 會需要先發現 name 是一個 property
再根據 property 的引數知道對應傳進去的函式, 再去找函式實作, 不太方便

Decorator 就是想辦法把這個 property 呼叫變得更簡明
當你在一個 method 上加上 @property 時, 例如這樣:

class Duck:
# 略

@property
def name(self):
return self._name

Python 會做以下的事情:

1. 根據 method 名 (這裡就是 name) 用一個同名 property 替代掉
2. 被替代掉的函式就當作該 property 的 fget 引數
3. property 的 doc 引數就是原本 getter 的 docstring

類似地, property.setter 會把被裝飾的函式設成該 property 的 setter

所以下面的實作

class Duck:
@property
def name(self):
return self._name

@name.setter
def name(self, name): # 順帶一提其實這個函式叫什麼根本不重要
self._name = name

大致等於

class Duck:
def getname(self):
return self._name

# @property 的作用
name = property(fget=getname, doc=getname.__doc__)
del getname

def setname(self, value):
self._name = name

# @name.setter 的作用
name.fset = setname
del setname

實際上因為 decorator 本身可以在賦名之前就作用
所以可以取和 property 一樣的名字
但是要討論這個就又要一千字, 所以這裡就不講到那裡
如果有興趣的話可以自己當成未來課題慢慢研究

另外從上面可以看到, property class 還有一個 deleter 可以設
所以實際上也還有一個 @property.deleter, 只是比較不常用
甚至實務上其實最常見的實作也只會用到 getter 而已
當你用到 setter 其實常常就代表需要重構了

--
GNUGCC:void main(void) 的寫法是可行的唷^^08/10 00:59
GNUGCC:雖然這個寫法較傳統,但是語法與文法都正確哦^^08/10 02:16
GNUGCC:目前我使用的 Visual C++ 都接受 void main(void) 與 08/10 20:18
GNUGCC:int main(void),各位可以把 C++ 專案改成原生 C++ 類型來 08/10 20:19
GNUGCC:用 void main(void) 來寫發現也可通過編譯. 08/10 20:21
GNUGCC:這個就是 Visual C++ 的彈性.08/11 20:23

--
※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 218.161.94.175
※ 文章網址: https://www.ptt.cc/bbs/Python/M.1514825287.A.BEE.html
ar0n77777: 大致上理解了,感謝u大解答!!太感謝了 01/02 01:20
aszx4510: 感謝教學 01/02 06:22
billy4195: 推個 01/02 07:13
timTan: 又帥又清楚,推推推 01/04 12:23
henry8168: 用心推推 01/04 12:27
fantasymaker: 推 01/05 16:34
twtkacc: 推 01/07 02:32
befdawn: 推 04/04 01:06
alan23273850: 年尾才看到有學習到給推 12/16 03:15
Philethan: WOW 02/02 23:53

你可能也想看看

搜尋相關網站