在面向對象編程中,封裝是核心原則之一——隱藏內部實現細節,僅通過受控接口與外界交互。Python 雖然沒有嚴格的“私有成員”,但通過 @property 裝飾器,我們可以以簡潔、Pythonic 的方式實現對類屬性的讀取、驗證和計算邏輯,既保持了代碼的直觀性,又增強了健壯性。本文將深入講解 @property 的用法、優勢及典型應用場景。
1. 為什麼需要 @property?
假設我們有一個表示温度的類:
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
用户可以直接修改 celsius,但如果傳入負數或非數字,可能導致邏輯錯誤。更糟的是,若後續需求變更(如需同時維護華氏温度),直接暴露屬性會讓重構變得困難。
理想做法是:將屬性訪問轉化為方法調用,但又不改變調用語法。這正是 @property 的價值所在。
2. 基礎用法:只讀屬性
使用 @property 將方法變為“只讀屬性”:
class Circle:
def __init__(self, radius):
self._radius = radius # 下劃線表示“內部使用”
@property
def area(self):
return 3.14159 * self._radius ** 2
c = Circle(5)
print(c.area) # ✅ 調用像屬性,實際執行方法
# c.area = 100 # ❌ AttributeError: can't set attribute
這裏,area 看似屬性,實則是動態計算的結果,且無法被意外修改。
3. 可讀寫屬性:配合 setter
通過 @<property_name>.setter,可定義賦值邏輯:
class Temperature:
def __init__(self, celsius=0):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if not isinstance(value, (int, float)):
raise ValueError("温度必須是數字")
if value < -273.15:
raise ValueError("温度不能低於絕對零度")
self._celsius = value
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32
# 使用
t = Temperature()
t.celsius = 25 # 觸發 setter 驗證
print(t.fahrenheit) # 77.0
# t.celsius = -300 # 拋出 ValueError
✅ 優勢:
- 數據驗證:確保屬性值合法;
- 接口穩定:即使內部邏輯變化,外部調用方式不變;
- 自動同步:修改
celsius時,fahrenheit自動反映新值。
4. 刪除屬性:deleter(較少用)
可通過 @<property_name>.deleter 定義刪除行為:
class Person:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.deleter
def name(self):
print("姓名不可刪除!")
# 或 raise AttributeError("Name is required")
p = Person("Alice")
del p.name # 輸出提示信息
5. 與傳統 getter/setter 對比
Java 風格的寫法(不推薦):
class BadExample:
def __init__(self, age):
self._age = age
def get_age(self): return self._age
def set_age(self, value): self._age = max(0, value)
# 調用繁瑣
person = BadExample(20)
person.set_age(25)
print(person.get_age())
而 @property 保持了屬性訪問的簡潔性,同時獲得方法的靈活性:
person.age = 25
print(person.age)
這正是 Python “顯式優於隱式,但簡潔優於冗餘”哲學的體現。
6. 實際應用場景
場景1:緩存昂貴計算結果
class DataProcessor:
def __init__(self, data):
self._data = data
self._cached_result = None
@property
def processed_data(self):
if self._cached_result is None:
# 模擬耗時計算
self._cached_result = [x * 2 for x in self._data]
return self._cached_result
場景2:兼容舊接口
當重構類時,可將原屬性轉為 property,避免破壞現有代碼:
# 舊代碼:obj.value
# 新實現:
@property
def value(self):
return self._new_internal_value
場景3:動態組合屬性
class User:
def __init__(self, first, last):
self.first_name = first
self.last_name = last
@property
def full_name(self):
return f"{self.first_name} {self.last_name}"
7. 注意事項
- 不要過度使用:簡單屬性無需 property,避免無謂複雜化;
- 避免副作用:property 方法應快速、無狀態,不應引發 I/O 或修改其他狀態;
- 命名一致性:property 名應與普通屬性無異,不要加
get_前綴。
@property 是 Python 中優雅實現“受控屬性訪問”的利器。它讓你在保持代碼簡潔的同時,輕鬆加入驗證、計算或緩存邏輯,是編寫高質量、可維護類設計的關鍵技巧。下次當你需要限制或增強對類成員的訪問時,不妨問問自己:“這裏是否該用 @property?” 答案很可能是肯定的。