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

作者: uranusjr (←這人是超級笨蛋)   2018-01-02 00:48:04
※ 引述《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 其實常常就代表需要重構了
作者: ar0n77777 (property)   2018-01-02 01:20:00
大致上理解了,感謝u大解答!!太感謝了
作者: GNUGCC (-std=c++14)   2018-08-10 00:59:00
void main(void) 的寫法是可行的唷^^雖然這個寫法較傳統,但是語法與文法都正確哦^^目前我使用的 Visual C++ 都接受 void main(void) 與int main(void),各位可以把 C++ 專案改成原生 C++ 類型來用 void main(void) 來寫發現也可通過編譯.這個就是 Visual C++ 的彈性.
作者: aszx4510 (wind)   2018-01-02 06:22:00
感謝教學
作者: billy4195 (Billy)   2018-01-02 07:13:00
推個
作者: timTan (用口頭禪區分年記)   2018-01-04 12:23:00
又帥又清楚,推推推
作者: henry8168 (番薯猴)   2018-01-04 12:27:00
用心推推
作者: fantasymaker   2018-01-05 16:34:00
作者: twtkacc (Tech)   2018-01-07 02:32:00

Links booklink

Contact Us: admin [ a t ] ucptt.com