Python 封裝 (Encapsulation)
封裝是物件導向程式設計的核心概念,用來隱藏物件的內部狀態和實作細節,只透過公開的介面與外界互動。
存取修飾符慣例
Python 沒有像其他語言(如 Java)那樣的 private、protected 關鍵字,而是使用命名慣例:
| 命名方式 | 意義 | 範例 |
|---|---|---|
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
封裝的好處
- 資料保護:防止外部直接修改內部狀態
- 驗證:在 setter 中加入驗證邏輯
- 靈活性:可以改變內部實作而不影響外部使用
- 維護性:將相關邏輯集中在類別內部