# 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')
```