封装是面向对象三大特性(封装、继承、多态)之一。它的核心思想是:隐藏对象的内部细节,只保留有限的对外接口,既降低了复杂度,又保护了数据安全。
一、封装基础概念
# 1. 为什么要封装?
- 保护数据:防止外部代码随意修改关键属性(如账户余额、年龄不能为负数)。
- 隔离变化:修改内部实现时,只要公开接口不变,外部代码无需改动。
- 简化使用:外部只需调用简单的方法,不需要了解复杂的内部逻辑。
# 2. 私有变量与私有方法
在Python 中,以双下划线 __ 开头(但不以双下划线结尾)的成员会被视为私有。Python 会对它们进行名称修饰(Name Mangling),将 __name 自动变成 _类名__name,从而阻止外部直接访问。
- 私有类变量:__interest_rate
- 私有实例变量:self.__amount
- 私有实例方法:def __get_info(self):
重要规则:私有成员只能在类的内部访问(包括私有方法内部),外部直接访问会引发AttributeError。
# 3. 访问私有成员的两种方式
- 传统 getter / setter 方法:通过定义公有的 get_xxx() 和 set_xxx() 方法来读写私有属性。优点是可以添加验证逻辑(如检查年龄是否合理)。
- 使用 @property 装饰器:将方法伪装成属性,使访问方式更自然:obj.age 和 obj.age = 新值。既保持了封装性,又提供了与直接访问属性相同的语法。
二、综合案例:银行账户(Account)的封装实现
# 完整代码
python
class Account:
# 私有类变量:银行利率(不可直接从外部修改)
__interest_rate = 0.025 # 2.5%
def __init__(self, owner, amount):
self.owner = owner # 公有实例变量(户主名)
self.__amount = amount # 私有实例变量(账户余额)
# 私有实例方法:返回账户信息字符串(外部不能直接调用)
def __get_info(self):
return f"{self.owner} 余额:{self.__amount} 利率:{Account.__interest_rate}"
# ---------- 公有方法:对外接口 ----------
def deposit(self, money):
if money > 0:
self.__amount += money
print(f"{self.owner} 存入 {money} 元,新余额 {self.__amount}")
else:
print("存款金额必须为正数")
def withdraw(self, money):
if 0 < money <= self.__amount:
self.__amount -= money
print(f"{self.owner} 取出 {money} 元,新余额 {self.__amount}")
else:
print("余额不足或金额无效")
def get_amount(self):
return self.__amount
def set_amount(self, amount):
if amount >= 0:
self.__amount = amount
else:
print("余额不能为负数")
@property
def amount(self):
return self.__amount
@amount.setter
def amount(self, amount):
if amount >= 0:
self.__amount = amount
else:
print("余额不能为负数")
def show_info(self):
print(self.__get_info())
# ---------- 测试代码 ----------
acc = Account("张三", 10000)
acc.deposit(2000)
acc.withdraw(500)
print("余额(getter):", acc.get_amount())
acc.set_amount(8000)
print("余额(setter后):", acc.get_amount())
print("余额(property):", acc.amount)
acc.amount = 12000
print("余额(property赋值后):", acc.amount)
acc.show_info()
运行输出:
张三存入2000 元,新余额 12000
张三取出500 元,新余额 11500
余额(getter): 11500
余额(setter后): 8000
余额(property): 8000
余额(property赋值后): 12000
张三余额:12000 利率:0.025
三、案例解析:封装的不同层次
以下列出本案例中各类成员的定义方式、访问规则及具体示例,无表格,便于复制。
- 公有实例变量
定义:self.owner = owner
内部访问:self.owner
外部访问:acc.owner
本案例示例:owner
- 私有实例变量
定义:self.__amount = amount
内部访问:self.__amount
外部访问:不可直接访问(acc.__amount 会报错)
本案例示例:__amount
- 私有类变量
定义:__interest_rate = 0.025
内部访问:Account.__interest_rate
外部访问:不可直接访问
本案例示例:__interest_rate
- 私有实例方法
定义:def __get_info(self):
内部访问:self.__get_info()
外部访问:不可直接调用
本案例示例:__get_info
- 传统 getter 方法
定义:def get_amount(self): return self.__amount
内部访问:self.get_amount()
外部访问:acc.get_amount()
本案例示例:get_amount
- 传统 setter 方法
定义:def set_amount(self, amount): self.__amount = amount
内部访问:self.set_amount(8000)
外部访问:acc.set_amount(8000)
本案例示例:set_amount
- property getter
定义:@property def amount(self): return self.__amount
内部访问:self.amount
外部访问:acc.amount
本案例示例:amount(只读)
- property setter
定义:@amount.setter def amount(self, amount): self.__amount = amount
内部访问:self.amount = 新值
外部访问:acc.amount = 新值
本案例示例:amount 的赋值
- 公有方法(内部调用私有成员)
定义:def show_info(self): print(self.__get_info())
内部访问:self.show_info()
外部访问:acc.show_info()
本案例示例:show_info
四、常见问题与最佳实践
1. 私有成员真的是“不可访问”吗?
Python 的私有是通过名称修饰实现的,仍然可以通过 _类名__变量名 访问,但这是一种约定,不应该这样做。正确的封装是信任调用者遵守规则。
2. 何时使用 getter/setter vs @property?
- 如果只需要简单的读写,且未来可能需要添加逻辑,使用 @property 最优雅。
- 如果需要在修改时执行复杂验证或触发其他操作,传统 getter/setter 更明显。
- 如果没有特殊需求,直接使用公有属性也可以,但为了封装性,建议将关键数据设为私有。
3. 为什么还要提供 setter?
直接暴露私有属性虽然可以赋值,但无法控制值的合法性。setter 可以在修改前进行检查,例如余额不能为负数。
4. 私有方法的作用是什么?
将内部复杂逻辑隐藏起来,外部只需要调用简单的公有方法。例如__get_info 被 show_info 调用,外部不需要知道内部字符串的格式。
五、总结
- 封装:隐藏内部细节,通过公开接口操作对象。
- 私有成员:以 __ 开头,限制外部直接访问,类内部自由使用。
- 访问私有成员的方式:
- 传统 getter / setter 方法(明确、可加验证)
- @property 装饰器(优雅、像属性一样使用)
- 设计原则:对外提供最小必要接口,内部实现可以随时优化而不影响外部代码。