Re: [問題] @property 真正的運用是啥

看板Python作者 (謊言接線生)時間4天前 (2025/01/15 12:03), 3天前編輯推噓4(4013)
留言17則, 7人參與, 12小時前最新討論串2/2 (看更多)
※ 引述《littrabble (littrabble)》之銘言: : @property : def name(self): : return self._name : @name.setter : def name(self, new_name): : self._name = new_name : 然後可以使用 instance p, : p.name 取值, p.name = 1 設值 : 我的疑問是, : 1. 這根本無法保護變數,為什麼教程還要說這種寫法保護變數 : 2. 加那個@property @name.setter, 到底有什麼好處? : 我如果不使用@property, 而是把方法名稱改成 get_name, 跟 set_name 程式碼讀起來,不是更清楚明白嗎? : 有沒有很有經驗的大大,能幫我解惑一下 : 感恩 我們從幾個角度來思考這個問題: 1. 語感 當我們使用 class Human 時,在普遍的語感上,屬性或成員是一些這個 class 實例具有的狀態或資訊: human1 = Human(...) print(human1.name) # 印出 human1 的 名字 而方法在語感上是一些行為或動作: human1.dance() # 讓 human1 進行 跳舞 這個行為 那我們來思考一下,如果我們採用 get_name,那印出姓名會是這樣的語感: print(human1.get_name()) # 讓 human1 進行 取得自己姓名 這個行為,然後印出 # 這個行為的結果 比較一下兩種印出名字的語感,是不是採用屬性或成員比較自然、不拐彎抹角? 然而,這也不代表 get/set method 形式就要完全捨棄。在 PEP 8 中有提到相 關的建議。 首先,如果是超級單純的直接成員存取,也沒有特殊的限制邏輯考量,則你應該 乾脆地直接使用公開成員,什麼 @property 或 get/set method 都免了。 再來,如果這個邏輯變得複雜,我們隨時都可以使用 @property 進行包裝,讓 使用方式跟公開成員完全相同,但內部處理邏輯改變。 但是,使用 @property 的情況下,因為其語感給使用者就像是直接存取一個成 員變數,所以我們會希望就算它有包裝一些處理邏輯,但這些處理邏輯不要帶來副作 用,也不要是太過昂貴的操作,因為使用者不會設想一個簡單的: human1.name = "ddavid" 操作背後居然會導致他的銀行帳戶變成我的,或者要執行三天只因為真的去跑戶 政事務所改名流程。當你真的想要讓上面兩件事情發生,使用 method 來表現的語感 就更為合適: human1.set_bank_account_name("ddavid") human1.set_id_card_name("ddavid") 法律小提示:銀行帳戶沒法轉讓啦,所以放心吧。直接轉帳給我就好啦(誤) 以下是 PEP 8 相關原文: For simple public data attributes, it is best to expose just the attribute name, without complicated accessor/mutator methods. Keep in mind that Python provides an easy path to future enhancement, should you find that a simple data attribute needs to grow functional behavior. In that case, use properties to hide functional implementation behind simple data attribute access syntax. Note 1: Try to keep the functional behavior side-effect free, although side-effects such as caching are generally fine. Note 2: Avoid using properties for computationally expensive operations; the attribute notation makes the caller believe that access is (relatively) cheap. 2. 保護變數 原 po 可能誤解的一點是,@property 的保護變數是跟直接暴露成員相比的。在 保護變數這一點上,它跟 set/get method 效果相差不大。 比如相較於: class Human: def __init__(height: float): self.height = height human1 = Human(170.1) human1.height = -1 # 亂給身高為負值 使用以下方法可以對此做出保護: class Human: def __init__(self, height: float): self._height = height @property def height(self): return self._height @height.setter def height(self, value: float): if value < 0: raise ValueError("Height cannot be negative") self._height = value 當然你一樣可以用 set_height 的寫法做到這一點: def set_height(self, value: float): if value < 0: raise ValueError("Height cannot be negative") self._height = value 但當考量到前述的語感理由,在 height 是個單純屬性處理的情況下,就沒什麼 必要強調操作性。 同時,我們也可以拿掉 setter/getter 其中之一,讓其變成可讀不可寫或可寫 不可讀,這也是一種保護。 當然我們知道,即便使用 _ 甚至 __ 前綴的成員,在 Python 中始終有手段直 接操作原始成員,因為 Python 把這些判斷留給 programmer。 3. 封裝邏輯 比如說,對於人類而言,BMI 語感上作為一個很單純的屬性值也很直覺。可是當 我們已經存了身高體重,額外存一個 BMI 好像在某些情況下有點多餘。於是我們就 可以在維持其屬性語感的前提下把邏輯包裝起來: class Human: def __init__(self, height: float, weight: float): self.height = height self.weight = weight @property def bmi(self): return self.weight / (self.height * self.height) 所以這麼做後,我們就可以用 human1.bmi 這樣直覺的方式取得這個人的 BMI, 而且在身高體重有變化時還可以自然跟著變化。而因為這不是很昂貴的運算,所以每 次取都算一下也沒太大關係。 同時,因為我們沒有給予 setter,也表達出了對於這個值的保護是唯讀的,我 們不能手改 BMI 或想藉由改 BMI 去影響身高體重值之類。 如前述,如果語感上要強調 BMI 每次都是計算出來的,我認為寫成 get_bmi 的 方法也無不可。 -- 「可是妳......不是天使嗎?」 「天使?」她緩緩的轉過頭來,用悲傷的表情。「天使,只不過是神創造出來的 不死玩偶。」 「而神,也只不過是詛咒下的偽善使者。」 --星.幻.夢的傳說 -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 125.229.62.213 (臺灣) ※ 文章網址: https://www.ptt.cc/bbs/Python/M.1736913822.A.4BF.html ※ 編輯: ddavid (125.229.62.213 臺灣), 01/15/2025 14:32:22

01/15 20:23, 3天前 , 1F
就…如果你寫的code也不是什麼大型專案,沒必要這樣設
01/15 20:23, 1F

01/15 20:23, 3天前 , 2F
計這些保護,除非設計理念跟有哲學上的潔癖(像我)
01/15 20:23, 2F
這倒不完全跟專案大小有關,跟系統是否接觸外部或者有其他協作者比較有關。

01/15 21:01, 3天前 , 3F
討論推
01/15 21:01, 3F
※ 編輯: ddavid (125.229.62.213 臺灣), 01/16/2025 10:46:49

01/16 11:41, 3天前 , 4F
我跟一樓一樣,寫程式有潔癖
01/16 11:41, 4F

01/17 07:33, 2天前 , 5F
獅子習慣良好
01/17 07:33, 5F

01/18 17:29, 18小時前 , 6F
但這樣的寫法使用者可能會以為自己在單純的賦值和取值,
01/18 17:29, 6F

01/18 17:29, 18小時前 , 7F
等到出錯才會意識到這不是一個單純的屬性,用getter/set
01/18 17:29, 7F

01/18 17:29, 18小時前 , 8F
ter明示不是比較不容易誤解嗎。
01/18 17:29, 8F

01/18 18:07, 18小時前 , 9F
在使用物件時,沒有經過 @property 裝飾器修飾的,無法這樣
01/18 18:07, 9F

01/18 18:07, 18小時前 , 10F
操作;為了實現物件導向程式開發的封裝概念,本來也不應該
01/18 18:07, 10F

01/18 18:07, 18小時前 , 11F
在 class 以外直接操作屬性。所以如果產生「我不知道是在直
01/18 18:07, 11F

01/18 18:07, 18小時前 , 12F
接操作屬性還是使用 getter 或 setter 耶」這樣的想法,需
01/18 18:07, 12F

01/18 18:07, 18小時前 , 13F
要回來想想看審視一下當下的寫法有沒有問題。
01/18 18:07, 13F

01/18 18:09, 18小時前 , 14F
我上面說的可能有些繞口,簡單來說就是在 OOP 的理念中,屬
01/18 18:09, 14F

01/18 18:09, 18小時前 , 15F
性本來就不該被直接操作。
01/18 18:09, 15F

01/18 23:30, 12小時前 , 16F
我試著比樓上更精確些。有封裝的概念時,
01/18 23:30, 16F

01/18 23:32, 12小時前 , 17F
我沒給的,外人本來就不能要;外人能拿到的代表有控制
01/18 23:32, 17F
文章代碼(AID): #1dXpEUI_ (Python)
討論串 (同標題文章)
文章代碼(AID): #1dXpEUI_ (Python)