Python 封裝 (Encapsulation)

封裝是物件導向程式設計的核心概念,用來隱藏物件的內部狀態和實作細節,只透過公開的介面與外界互動。

存取修飾符慣例

Python 沒有像其他語言(如 Java)那樣的 privateprotected 關鍵字,而是使用命名慣例:

命名方式意義範例
name公開 (public)self.name
_name約定私有 (protected)self._name
__name名稱改編 (private)self.__name

公開屬性

沒有底線開頭的屬性是公開的,可以自由存取和修改:

class Person:
    def __init__(self, name, age):
        self.name = name  # 公開
        self.age = age    # 公開

p = Person("Alice", 25)
print(p.name)  # Alice
p.name = "Bob"  # 可以直接修改
print(p.name)  # Bob

約定私有 (單底線)

單底線開頭表示「這是內部使用的,請不要直接存取」,但技術上仍可存取:

class BankAccount:
    def __init__(self, balance):
        self._balance = balance  # 約定私有
    
    def deposit(self, amount):
        self._balance += amount
    
    def get_balance(self):
        return self._balance

account = BankAccount(1000)

# 技術上可以存取,但不建議
print(account._balance)  # 1000

# 應該使用方法存取
print(account.get_balance())  # 1000

名稱改編 (雙底線)

雙底線開頭會觸發名稱改編 (name mangling),Python 會將 __name 改成 _ClassName__name

class Secret:
    def __init__(self):
        self.__private = "secret data"
    
    def get_private(self):
        return self.__private

s = Secret()

# 無法直接存取
# print(s.__private)  # AttributeError

# 透過方法存取
print(s.get_private())  # secret data

# 名稱改編後仍可存取(但強烈不建議)
print(s._Secret__private)  # secret data

繼承時的名稱改編

名稱改編可以避免子類別意外覆寫父類別的屬性:

class Parent:
    def __init__(self):
        self.__value = "parent"
    
    def get_value(self):
        return self.__value

class Child(Parent):
    def __init__(self):
        super().__init__()
        self.__value = "child"  # 這是 _Child__value,不是 _Parent__value
    
    def get_child_value(self):
        return self.__value

c = Child()
print(c.get_value())        # parent(父類別的 __value)
print(c.get_child_value())  # child(子類別的 __value)

Property 裝飾器

使用 @property 提供受控的屬性存取:

class Temperature:
    def __init__(self, celsius=0):
        self._celsius = celsius
    
    @property
    def celsius(self):
        return self._celsius
    
    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError("Temperature below absolute zero!")
        self._celsius = value
    
    @property
    def fahrenheit(self):
        return self._celsius * 9/5 + 32
    
    @fahrenheit.setter
    def fahrenheit(self, value):
        self.celsius = (value - 32) * 5/9

temp = Temperature(25)
print(temp.celsius)     # 25
print(temp.fahrenheit)  # 77.0

temp.fahrenheit = 100
print(temp.celsius)     # 37.777...

# temp.celsius = -300  # ValueError

Getter 和 Setter 方法

傳統的 getter/setter 方法:

class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age
    
    # Getter
    def get_name(self):
        return self._name
    
    def get_age(self):
        return self._age
    
    # Setter
    def set_name(self, name):
        if not name:
            raise ValueError("Name cannot be empty")
        self._name = name
    
    def set_age(self, age):
        if age < 0:
            raise ValueError("Age cannot be negative")
        self._age = age

p = Person("Alice", 25)
print(p.get_name())  # Alice
p.set_age(30)
print(p.get_age())   # 30
在 Python 中,使用 @property 比傳統的 getter/setter 更 Pythonic。

只讀屬性

只定義 getter,不定義 setter:

class Circle:
    def __init__(self, radius):
        self._radius = radius
    
    @property
    def radius(self):
        return self._radius
    
    @property
    def area(self):
        import math
        return math.pi * self._radius ** 2

circle = Circle(5)
print(circle.radius)  # 5
print(circle.area)    # 78.53...

# circle.radius = 10  # AttributeError: can't set attribute
# circle.area = 100   # AttributeError: can't set attribute

實際範例

class User:
    def __init__(self, username, email, password):
        self._username = username
        self._email = email
        self.__password = self._hash_password(password)
    
    def _hash_password(self, password):
        # 簡化的密碼雜湊(實際應使用專門的函式庫)
        import hashlib
        return hashlib.sha256(password.encode()).hexdigest()
    
    @property
    def username(self):
        return self._username
    
    @property
    def email(self):
        return self._email
    
    @email.setter
    def email(self, value):
        if '@' not in value:
            raise ValueError("Invalid email format")
        self._email = value
    
    def verify_password(self, password):
        return self.__password == self._hash_password(password)
    
    def change_password(self, old_password, new_password):
        if not self.verify_password(old_password):
            raise ValueError("Incorrect password")
        if len(new_password) < 8:
            raise ValueError("Password must be at least 8 characters")
        self.__password = self._hash_password(new_password)
        print("Password changed successfully")

# 使用
user = User("alice", "alice@example.com", "secret123")

print(user.username)  # alice
print(user.email)     # alice@example.com

# 無法直接存取密碼
# print(user.__password)  # AttributeError

# 驗證密碼
print(user.verify_password("secret123"))  # True
print(user.verify_password("wrong"))      # False

# 修改密碼
user.change_password("secret123", "newpassword123")
print(user.verify_password("newpassword123"))  # True

封裝的好處

  1. 資料保護:防止外部直接修改內部狀態
  2. 驗證:在 setter 中加入驗證邏輯
  3. 靈活性:可以改變內部實作而不影響外部使用
  4. 維護性:將相關邏輯集中在類別內部