在面向對象編程中,封裝是核心原則之一——隱藏內部實現細節,僅通過受控接口與外界交互。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?” 答案很可能是肯定的。