Re: [問題] @property 真正的運用是啥
※ 引述《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
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
01/18 17:29, 7F
→
01/18 17:29,
18小時前
, 8F
01/18 17:29, 8F
→
01/18 18:07,
18小時前
, 9F
01/18 18:07, 9F
→
01/18 18:07,
18小時前
, 10F
01/18 18:07, 10F
→
01/18 18:07,
18小時前
, 11F
01/18 18:07, 11F
→
01/18 18:07,
18小時前
, 12F
01/18 18:07, 12F
→
01/18 18:07,
18小時前
, 13F
01/18 18:07, 13F
→
01/18 18:09,
18小時前
, 14F
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
討論串 (同標題文章)
本文引述了以下文章的的內容:
完整討論串 (本文為第 2 之 2 篇):
Python 近期熱門文章
PTT數位生活區 即時熱門文章