# Python Property 屬性 如有錯誤歡迎糾正,小弟正在學習中 ## property property 是一種用來管理對象屬性訪問的機制。它允許你使用方法來管理屬性讀取、寫入和刪除,而不需要直接調用方法。這樣做可以使屬性操作更直觀,並提供更多的控制和封裝,而在python中所有property都是public,可以透過以下例子來驗證 例子 school為內部會使用的property ``` python class Student(object): def __init__(self, name, age, school): self.name = name self.age = age self._school = school student_info = Student(name='Jed', age=18, school='Python_School') ``` property皆public,可以看到student_info.__dict__能看到_school property ``` python print(student_info.__dict__) >>> {'name': 'Jed', 'age': 18, '_school': 'Python_School'} ``` ### _(底線)、__(雙底線) (底線)表示這是一個"內部"屬性或方法,僅供類內部或子類使用。它是一種約定,提醒其他開發者這個屬性或方法不應該被直接訪問或修改,但這並不是強制的限制。通常會出現在希望在內部class使用就會宣告_(底線)在變數前方,而__(雙底線)很多人以為是真正設置private方式,其實是大錯特錯的我們來看範例 ``` python class Student(object): def __init__(self, name, age, school): self.name = name self.age = age self.__school = school student_info = Student(name='Jed', age=18, school='Python_School') print(student_info._school) ``` 當取得student_info.school會得到, ``` python print(student_info.__school) >>> AttributeError: 'Student' object has no attribute '__school' ``` 但他真的變private嗎?請記住前面所說property皆public,我們馬上來驗證 ``` python print(student_info.__dict__) >>> {'name': 'Jed', 'age': 18, '_Student__school': 'Python_School'} ``` 會發現key居然改名了,這是python使用__(雙底線)特性會自動修改名稱,避免子類別意外覆蓋父類別的屬性或方法,所以並不是property private了,而是名稱改變無法存取property,驗證這件事只要把名稱改成_Student__school就能看到value了 ``` python print(student_info._Student__school) >>> Python_School ``` > 💡__(雙底線)並不是pythonic作法,且請不要使用__(雙底線),如果要表示class內部使用意義請用_(底線) ### @property decorator property在物件導向設計中,有些時候想封裝行為、驗證與保護數值避免被修改時,常被使用 例子: 建立個人資訊class,需要輸入名稱、年齡、身高、體重、並驗證年齡合理性 ``` python class Person: def __init__(self, name, age, weight, height): self.name = name self.person_age = age self.weight = weight self.height = height @property def bmi(self): height_m = self.height / 100 return round(self.weight / (height_m ** 2),2) @property def age(self): return self.person_age @age.setter def age(self, value): if value <= 0: raise ValueError("Age cannot be negative.") self.person_age = value person = Person(name='Jed', age=26, weight=75, height=172) print(person.name, person.age, person.bmi) >>> Jed 26 25.35 ``` - 封裝行為與保護property數值被修改 在IBM部分,在初始化時將計算行為隱藏並封裝property,讓使用者更直觀去使用claas attritube ``` python @property def bmi(self): height_m = self.height / 100 return round(self.weight / (height_m ** 2),2) ``` 避免使用竄改BMI數據 ``` python person = Person(name='Jed', age=26, weight=75, height=172) person.bmi = 20.22 print(person.bmi) >>> person.bmi = 20.22 AttributeError: can't set attribute ``` - 驗證value合法性,與保護參數數值 透過setter來驗證數值合理性 ``` python @property def age(self): return self.person_age @age.setter def age(self, value): if value <= 0: raise ValueError("Age cannot be negative.") self.person_age = value ``` 當數值驗證不正確時,raise Error ``` python person = Person(name='Jed', age=26, weight=75, height=172) person.age = -1 >>> raise ValueError("Age cannot be negative.") ValueError: Age cannot be negative. ``` >💡關於@property的驗證請適當使用,像是簡單的情境不是很需要複雜的驗證就避免使用,以免增加維護性的困擾與降低可讀性 ### Lazy Property lazy property 是一種有效的性能優化技術,通過推遲計算,直到真正需要時才計算,能夠節省計算資源和提高程序效率。使用 @property 裝飾器來實現懶加載屬性,能夠簡化代碼並隱藏計算邏輯。 #### 使用場景 - 計算成本高昂的屬性。 - 不經常訪問或有條件才需要計算的屬性。 - 在對象的生命周期內,屬性值不頻繁變化。 - 需要懶加載以提高性能的屬性,例如涉及外部資源的屬性。 例子: 假設計算BMI每次計算是需要花費5秒,且不會變動具有幂等性,那就非常適合做lazy property ``` python import time class Person: def __init__(self, name, age, weight, height): self.name = name self.person_age = age self.weight = weight self.height = height self._bmi = None # 用於懶加載的屬性 @property def bmi(self): if self._bmi is None: # 懶加載 start_time = time.time() # 計算開始時間 time.sleep(5) # 模擬昂貴的計算過程 height_m = self.height / 100 self._bmi = round(self.weight / (height_m ** 2), 2) end_time = time.time() # 計算結束時間 self._last_calculation_time = end_time - start_time return self._bmi @property def age(self): return self.person_age @age.setter def age(self, value): if value <= 0: raise ValueError("Age cannot be negative.") self.person_age = value # 使用示例 person = Person(name='Jed', age=26, weight=75, height=172) ``` 第一次使用時擷取property需花費五秒 ``` python # 第一次計算 BMI,並會存儲結果 start_time = time.time() print(f"BMI: {person.bmi:.2f}") print(f"BMI 計算耗時: {time.time() - start_time:.2f} 秒") print() start_time = time.time() >>> BMI: 25.35 BMI 計算耗時: 5.00 秒 ``` 第二次擷取時,因為實作lazy property,所以花費時間約0秒 ``` python # 第二次直接返回存儲的結果 print(f"BMI: {person.bmi:.2f}") print(f"BMI 計算耗時: {time.time() - start_time:.2f} 秒") >>> BMI: 25.35 BMI 計算耗時: 0.00 秒 ``` ### Function vs Property 當不須傳遞數值,也不須設置條件時適合用property,如果有條件或者邏輯處理複雜就是合用function 例子(適合property場景): 加上是否滿18歲,以function來看需要調用且不用傳遞參數,反而不簡潔也不直觀,定義為property較為簡潔 ``` python class Person: def __init__(self, name, age, weight, height): self.name = name self.person_age = age self.weight = weight self.height = height self._bmi = None @property def is_over_18(self): return self.age > 18 def is_over_18_f(self): return self.age > 18 ``` 初始化後使用property與function來處理業務邏輯,會發現property會比較簡潔且明確 ``` python person = Person(name='Jed', age=26, weight=75, height=172) if person.is_over_18: print('can buy beer') if person.is_over_18_f(): print('can buy beer') ``` 例子(適合function場景): 現在改成判斷是否可以投票,如果使用property會將行為隱藏,但未來可能會因法規改變而判斷條件也會變動,假設浮誇點每個月都會改變可投票歲數,那這種場景就不適合property,因為每次變動都需要改動property邏輯,沒有保留靈活性 ``` python class Person: def __init__(self, name, age, weight, height): self.name = name self.person_age = age self.weight = weight self.height = height self._bmi = None # 用於懶加載的屬性 @property def can_vote(self): return self.age >20 def is_older_than_f(self, age_threshold): return self.age > age_threshold ``` 判別是否可以投票 ``` python person = Person(name='Jed', age=26, weight=75, height=172) if person.can_vote: print('can vote') if person.is_older_than_f(20): print('can vote') ``` function命名不值直觀時,可以定義變數來明確定義,假設法規修法成21歲才能投票也能快速調整,無須動到內部邏輯,提高靈活性 ``` python cna_vote = person.is_older_than_f(21) if cna_vote: print('can vote') ```