封装是面向对象编程的三大特性之一,它隐藏对象的内部实现细节,仅对外暴露必要的接口。Python 通过多种机制实现封装。
一、封装的基本概念
1. 为什么需要封装?
# 没有封装的问题示例class BadBankAccount: def __init__(self, balance): self.balance = balance # 公共属性,可以直接修改account = BadBankAccount(1000)account.balance = -1000 # 可以直接设置为负数,违反业务规则print(account.balance) # 输出: -1000# 使用封装解决class GoodBankAccount: def __init__(self, balance): self._balance = balance # 保护属性 def get_balance(self): """获取余额""" return self._balance def deposit(self, amount): """存款""" if amount <= 0: raise ValueError("存款金额必须大于0") self._balance += amount def withdraw(self, amount): """取款""" if amount <= 0: raise ValueError("取款金额必须大于0") if amount > self._balance: raise ValueError("余额不足") self._balance -= amountaccount = GoodBankAccount(1000)# account._balance = -1000 # 技术上可以,但不应该这样做account.withdraw(200) # 必须通过方法操作print(account.get_balance()) # 输出: 800
二、Python 封装的三个层次
1. 公有成员(Public)
class PublicExample: def __init__(self): self.public_attr = "公有属性" # 可以直接访问 def public_method(self): """公有方法""" return "公有方法"obj = PublicExample()print(obj.public_attr) # 正常访问print(obj.public_method()) # 正常调用
2. 保护成员(Protected)
class ProtectedExample: def __init__(self): self._protected_attr = "保护属性" # 单下划线前缀 def _protected_method(self): """保护方法""" return "保护方法" def public_method(self): """公有方法可以访问保护成员""" return f"通过公有方法访问: {self._protected_attr}"obj = ProtectedExample()print(obj._protected_attr) # 可以访问,但不推荐print(obj.public_method()) # 推荐的方式
3. 私有成员(Private)
class PrivateExample: def __init__(self): self.__private_attr = "私有属性" # 双下划线前缀 self._protected_attr = "保护属性" def __private_method(self): """私有方法""" return "私有方法" def public_method(self): """访问私有成员""" return f"私有属性值: {self.__private_attr}" def get_private_attr(self): """获取器方法""" return self.__private_attr def set_private_attr(self, value): """设置器方法""" if isinstance(value, str): self.__private_attr = value else: raise ValueError("必须是字符串")obj = PrivateExample()# print(obj.__private_attr) # 错误:AttributeError# print(obj.__private_method()) # 错误:AttributeErrorprint(obj.public_method()) # 输出: 私有属性值: 私有属性print(obj.get_private_attr()) # 通过方法访问# Python 的名称修饰(Name Mangling)print(obj._PrivateExample__private_attr) # 可以访问,但不应该这样做# 查看对象的所有属性print(dir(obj)) # 可以看到 _PrivateExample__private_attr
三、封装的实现方法
1. 使用属性(property)封装
class Student: def __init__(self, name, age, score): self._name = name self._age = age self._score = score # 使用property封装name属性 @property def name(self): """获取姓名""" return self._name @name.setter def name(self, value): """设置姓名(带验证)""" if not isinstance(value, str): raise TypeError("姓名必须是字符串") if len(value.strip()) == 0: raise ValueError("姓名不能为空") self._name = value # 使用property封装age属性 @property def age(self): return self._age @age.setter def age(self, value): if not isinstance(value, int): raise TypeError("年龄必须是整数") if value < 0 or value > 150: raise ValueError("年龄必须在0-150之间") self._age = value # 只读属性(只有getter,没有setter) @property def score(self): return self._score # 计算属性 @property def grade(self): """根据分数计算等级""" if self._score >= 90: return "优秀" elif self._score >= 80: return "良好" elif self._score >= 60: return "及格" else: return "不及格"# 使用student = Student("张三", 20, 85)print(f"姓名: {student.name}")print(f"等级: {student.grade}")student.name = "李四" # 使用setterstudent.age = 21 # 使用setter# student.score = 90 # 错误:只读属性# 验证try: student.age = -5 # 触发验证except ValueError as e: print(f"错误: {e}")
2. 使用描述符(Descriptor)封装
class ValidatedAttribute: """验证描述符""" def __init__(self, name, expected_type, min_value=None, max_value=None): self.name = name self.expected_type = expected_type self.min_value = min_value self.max_value = max_value def __get__(self, obj, objtype): if obj is None: return self # 从实例的__dict__中获取值 return obj.__dict__.get(self.name) def __set__(self, obj, value): # 类型验证 if not isinstance(value, self.expected_type): raise TypeError(f"{self.name} 必须是 {self.expected_type.__name__} 类型") # 范围验证 if self.min_value is not None and value < self.min_value: raise ValueError(f"{self.name} 不能小于 {self.min_value}") if self.max_value is not None and value > self.max_value: raise ValueError(f"{self.name} 不能大于 {self.max_value}") # 存储到实例的__dict__ obj.__dict__[self.name] = valueclass Person: # 使用描述符定义属性 name = ValidatedAttribute("name", str) age = ValidatedAttribute("age", int, min_value=0, max_value=150) height = ValidatedAttribute("height", float, min_value=0.5, max_value=2.5) def __init__(self, name, age, height): self.name = name # 触发描述符的__set__ self.age = age self.height = height# 使用person = Person("张三", 25, 1.75)print(f"{person.name}, {person.age}岁, {person.height}米")# 验证try: person.age = -5 # 触发验证except ValueError as e: print(f"验证错误: {e}")try: person.name = 123 # 触发类型验证except TypeError as e: print(f"类型错误: {e}")
3. 使用类方法工厂
class BankAccount: def __init__(self, account_number, owner, balance=0): self._account_number = account_number self._owner = owner self._balance = balance self._transactions = [] @classmethod def create_account(cls, account_number, owner, initial_deposit=0): """工厂方法:创建账户""" if initial_deposit < 0: raise ValueError("初始存款不能为负数") return cls(account_number, owner, initial_deposit) def deposit(self, amount): """存款""" if amount <= 0: raise ValueError("存款金额必须大于0") self._balance += amount self._transactions.append(f"存款: +{amount}") return self._balance def withdraw(self, amount): """取款""" if amount <= 0: raise ValueError("取款金额必须大于0") if amount > self._balance: raise ValueError("余额不足") self._balance -= amount self._transactions.append(f"取款: -{amount}") return self._balance def get_balance(self): """获取余额""" return self._balance def get_statement(self): """获取对账单(不包含余额)""" return self._transactions.copy()# 使用工厂方法创建对象account = BankAccount.create_account("123456", "张三", 1000)print(f"初始余额: {account.get_balance()}")account.deposit(500)account.withdraw(200)print(f"当前余额: {account.get_balance()}")print(f"交易记录: {account.get_statement()}")
四、高级封装模式
1. 组合模式封装
class Engine: def __init__(self, horsepower): self._horsepower = horsepower def start(self): return f"{self._horsepower}马力引擎启动" def stop(self): return "引擎停止"class Wheel: def __init__(self, size): self._size = size def rotate(self): return f"{self._size}寸轮子转动"class Car: def __init__(self, brand, engine_hp, wheel_size): self._brand = brand self._engine = Engine(engine_hp) # 组合:拥有引擎对象 self._wheels = [Wheel(wheel_size) for _ in range(4)] # 组合:拥有轮子对象 self._is_running = False def start(self): if not self._is_running: self._is_running = True return f"{self._brand}启动: {self._engine.start()}" return "汽车已经在运行" def drive(self): if self._is_running: wheel_actions = [wheel.rotate() for wheel in self._wheels] return f"{self._brand}行驶中: {', '.join(wheel_actions)}" return "请先启动汽车" def stop(self): if self._is_running: self._is_running = False return f"{self._brand}停止: {self._engine.stop()}" return "汽车已经停止"# 使用car = Car("丰田", 150, 18)print(car.start())print(car.drive())print(car.stop())
2. 门面模式(Facade)封装
class CPU: def execute(self): return "CPU执行指令"class Memory: def load(self): return "内存加载数据"class HardDrive: def read(self): return "硬盘读取数据"class ComputerFacade: """计算机门面类,封装复杂子系统""" def __init__(self): self._cpu = CPU() self._memory = Memory() self._hard_drive = HardDrive() def start(self): """启动计算机的简化接口""" results = [] results.append("启动电源") results.append(self._hard_drive.read()) results.append(self._memory.load()) results.append(self._cpu.execute()) results.append("计算机启动完成") return results def shutdown(self): """关闭计算机的简化接口""" return ["保存数据", "关闭系统", "断电"]# 使用computer = ComputerFacade()print("启动计算机:")for step in computer.start(): print(f" {step}")print("\n关闭计算机:")for step in computer.shutdown(): print(f" {step}")
3. 代理模式封装
class RealDatabase: """真实数据库(访问成本高)""" def __init__(self): self._data = {"user1": "data1", "user2": "data2"} def query(self, user_id): print(f"真实数据库查询: {user_id}") # 模拟耗时操作 import time time.sleep(1) return self._data.get(user_id, "未找到")class DatabaseProxy: """数据库代理(添加缓存和访问控制)""" def __init__(self, real_database): self._real_database = real_database self._cache = {} self._access_log = [] def query(self, user_id, user_role="guest"): """查询数据库(带权限检查和缓存)""" # 访问控制 if user_role not in ["admin", "user"]: raise PermissionError(f"角色 {user_role} 无权限访问") # 记录访问日志 self._access_log.append({ 'user': user_id, 'role': user_role, 'time': '2023-12-01 10:00:00' }) # 检查缓存 if user_id in self._cache: print(f"从缓存获取: {user_id}") return self._cache[user_id] # 缓存未命中,查询真实数据库 result = self._real_database.query(user_id) # 缓存结果 self._cache[user_id] = result return result def get_access_log(self): """获取访问日志(只有管理员能看到)""" return self._access_log.copy()# 使用real_db = RealDatabase()proxy = DatabaseProxy(real_db)print("第一次查询(访问真实数据库):")result = proxy.query("user1", "user")print(f"结果: {result}")print("\n第二次查询(从缓存获取):")result = proxy.query("user1", "user")print(f"结果: {result}")print("\n获取访问日志:")for log in proxy.get_access_log(): print(f" {log}")
五、封装的实用技巧
1. 使用 @property 实现只读属性
class Constants: """常量类(使用只读属性封装)""" @property def PI(self): return 3.141592653589793 @property def E(self): return 2.718281828459045 @property def GRAVITY(self): return 9.80665 # m/s²# 使用constants = Constants()print(f"π = {constants.PI}")print(f"e = {constants.E}")print(f"重力加速度 = {constants.GRAVITY} m/s²")# constants.PI = 3.14 # 错误:只读属性
2. 使用 __slots__ 限制属性
class RestrictedPerson: """使用__slots__限制可以添加的属性""" __slots__ = ['_name', '_age', '_height'] # 只能有这三个属性 def __init__(self, name, age, height): self._name = name self._age = age self._height = height @property def name(self): return self._name @property def age(self): return self._age @property def height(self): return self._height# 使用person = RestrictedPerson("张三", 25, 1.75)print(f"{person.name}, {person.age}岁, {person.height}米")# person.weight = 70 # 错误:'RestrictedPerson' object has no attribute 'weight'# person._weight = 70 # 同样错误:受__slots__限制# 内存节省(特别适合创建大量实例的情况)import sysclass NormalPerson: def __init__(self, name, age): self.name = name self.age = agenormal = NormalPerson("张三", 25)restricted = RestrictedPerson("李四", 25, 1.75)print(f"普通对象内存: {sys.getsizeof(normal) + sys.getsizeof(normal.__dict__)} 字节")print(f"__slots__对象内存: {sys.getsizeof(restricted)} 字节")
3. 使用上下文管理器封装资源
class DatabaseConnection: """数据库连接封装""" def __init__(self, host, port, database): self.host = host self.port = port self.database = database self._connection = None def __enter__(self): """进入上下文时连接数据库""" print(f"连接到 {self.host}:{self.port}/{self.database}") self._connection = "模拟连接对象" return self def __exit__(self, exc_type, exc_val, exc_tb): """退出上下文时关闭连接""" print("关闭数据库连接") self._connection = None if exc_type: print(f"发生错误: {exc_val}") return True # 抑制异常 def execute_query(self, sql): """执行查询""" if not self._connection: raise ConnectionError("数据库未连接") print(f"执行SQL: {sql}") return "查询结果"# 使用(自动管理资源)with DatabaseConnection("localhost", 5432, "mydb") as db: result = db.execute_query("SELECT * FROM users") print(f"结果: {result}")# 退出with块后自动关闭连接
六、封装的最佳实践
1. 遵循最小暴露原则
class WellEncapsulated: """良好封装的示例""" def __init__(self, data): self._data = data # 保护属性 self._processed = False self._result = None def process(self): """处理数据(公有接口)""" self._preprocess() self._transform() self._postprocess() self._processed = True return self._result def _preprocess(self): """预处理(保护方法)""" # 实现细节被隐藏 pass def _transform(self): """转换(保护方法)""" pass def _postprocess(self): """后处理(保护方法)""" passclass BadlyEncapsulated: """封装不佳的示例""" def __init__(self): self.data = None # 公有属性 self.temp_result = None # 公有属性 self.final_result = None # 公有属性 def do_everything(self): """所有操作都在一个方法中""" # 太多实现细节暴露在公有方法中 pass
2. 优先使用属性而非公有字段
# 不推荐class BadDesign: def __init__(self): self.value = 0 # 公有字段# 推荐class GoodDesign: def __init__(self): self._value = 0 # 保护字段 @property def value(self): return self._value @value.setter def value(self, val): # 可以添加验证逻辑 self._value = val
总结
Python 封装的核心要点:
封装实现方法:
命名约定:最简单的封装方式
property装饰器:最常用的属性封装
描述符:高级属性控制
特殊方法:__slots__、__getattr__等
设计模式:工厂、门面、代理等模式
最佳实践:
所有实例变量都应该是保护或私有
使用property提供访问接口
在setter中添加验证逻辑
遵循"最小暴露原则"
为公有接口编写文档
良好的封装可以提高代码的:
安全性:防止非法访问和修改
可维护性:修改实现不影响接口
可读性:清晰的接口设计
可测试性:易于单元测试