Re: [問題] 初學@property之疑問

看板Python作者 (←這人是超級笨蛋)時間7年前 (2018/01/02 00:48), 編輯推噓9(901)
留言10則, 10人參與, 最新討論串2/2 (看更多)
※ 引述《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 其實常常就代表需要重構了 --

08/10 00:59,
void main(void) 的寫法是可行的唷^^
08/10 00:59

08/10 02:16,
雖然這個寫法較傳統,但是語法與文法都正確哦^^
08/10 02:16

08/10 20:18,
目前我使用的 Visual C++ 都接受 void main(void) 與
08/10 20:18

08/10 20:19,
int main(void),各位可以把 C++ 專案改成原生 C++ 類型來
08/10 20:19

08/10 20:21,
用 void main(void) 來寫發現也可通過編譯.
08/10 20:21

08/11 20:23,
這個就是 Visual C++ 的彈性.
08/11 20:23
-- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 218.161.94.175 ※ 文章網址: https://www.ptt.cc/bbs/Python/M.1514825287.A.BEE.html

01/02 01:20, 7年前 , 1F
大致上理解了,感謝u大解答!!太感謝了
01/02 01:20, 1F

01/02 06:22, 7年前 , 2F
感謝教學
01/02 06:22, 2F

01/02 07:13, 7年前 , 3F
推個
01/02 07:13, 3F

01/04 12:23, 7年前 , 4F
又帥又清楚,推推推
01/04 12:23, 4F

01/04 12:27, 7年前 , 5F
用心推推
01/04 12:27, 5F

01/05 16:34, 7年前 , 6F
01/05 16:34, 6F

01/07 02:32, 7年前 , 7F
01/07 02:32, 7F

04/04 01:06, 7年前 , 8F
04/04 01:06, 8F

12/16 03:15, 6年前 , 9F
年尾才看到有學習到給推
12/16 03:15, 9F

02/02 23:53, , 10F
WOW
02/02 23:53, 10F
文章代碼(AID): #1QIcP7lk (Python)
討論串 (同標題文章)
文章代碼(AID): #1QIcP7lk (Python)