编程与数学 02-017 Python 面向对象编程 05课题、封装
- 一、封装的核心思想
- 二、Python 实现封装的三种方式
-
-
- (1) 命名约定
- (2) 使用 `@property` 控制访问
- (3) 通过方法暴露操作
-
- 三、封装的优点
- 四、封装的常见应用场景
-
-
- (1) 数据验证
- (2) 只读属性
- (3) 延迟加载(Lazy Loading)
-
- 五、注意事项
- 六、封装 vs 非封装对比
-
-
- 非封装示例(风险高)
- 封装示例(安全)
-
- 全文总结
摘要:封装通过命名约定、@property和方法暴露三种方式隐藏实现细节,防止非法修改,简化接口;优点是数据保护、低耦合、易维护、高灵活性,适用于数据验证、只读属性与延迟加载等场景。
关键词:封装、Python、命名约定、@property、数据保护
人工智能助手:Kimi
一、封装的核心思想
- 隐藏实现细节:只暴露必要的操作接口,内部逻辑对用户透明。
- 数据保护:防止外部直接修改对象内部状态,避免非法操作。
- 简化使用:用户只需知道“做什么”,无需关心“怎么做”。
二、Python 实现封装的三种方式
Python 没有严格的 private 或 protected 关键字,但通过以下约定实现封装:
(1) 命名约定
- 公有成员:直接公开(如 name)。
- 受保护成员:单下划线 _ 前缀(约定俗成,非强制)。
- 私有成员:双下划线 __ 前缀(触发名称修饰,避免直接访问)。
class BankAccount:
def __init__(self, balance):
self.balance = balance # 公有属性
self._account_id = "123" # 受保护属性(约定)
self.__pin = "0000" # 私有属性(名称修饰为 _BankAccount__pin)
account = BankAccount(1000)
print(account.balance) # 正常访问:1000
print(account._account_id) # 仍可访问(但不推荐):123
print(account.__pin) # 报错!实际需访问 account._BankAccount__pin
(2) 使用 @property 控制访问
- 将方法伪装成属性,通过 getter/setter 添加逻辑。
- 优点:保持接口一致性,同时实现数据验证。
class Temperature:
def __init__(self, celsius):
self._celsius = celsius # 保护属性
@property
def celsius(self): # Getter
return self._celsius
@celsius.setter
def celsius(self, value): # Setter(可添加校验)
if value < –273.15:
raise ValueError("温度不能低于绝对零度!")
self._celsius = value
@property
def fahrenheit(self): # 计算属性(只读)
return self._celsius * 9/5 + 32
temp = Temperature(25)
print(temp.celsius) # 25
temp.celsius = 30 # 通过 setter 修改
print(temp.fahrenheit) # 86.0(只读属性)
temp.celsius = –300 # 触发 ValueError
(3) 通过方法暴露操作
禁止直接修改属性,强制通过方法操作数据。
class LightSwitch:
def __init__(self):
self.__is_on = False # 私有属性
def turn_on(self): # 公有方法
self.__is_on = True
def turn_off(self):
self.__is_on = False
def status(self):
return "开" if self.__is_on else "关"
switch = LightSwitch()
switch.turn_on()
print(switch.status()) # 输出: 开
# switch.__is_on = True # 报错!无法直接访问私有属性
三、封装的优点
数据保护 | 防止外部代码意外修改关键数据(如银行账户余额)。 |
降低耦合 | 隐藏实现细节,修改内部逻辑不影响外部调用。 |
易于维护 | 代码模块化,错误更容易定位。 |
灵活性 | 可在 getter/setter 中添加额外逻辑(如日志、校验)。 |
四、封装的常见应用场景
(1) 数据验证
class User:
def __init__(self, username):
self.username = username # 通过 setter 校验
@property
def username(self):
return self.__username
@username.setter
def username(self, value):
if not value.isalnum():
raise ValueError("用户名只能包含字母和数字")
self.__username = value
user = User("Alice123")
user.username = "Bob!" # 触发 ValueError
(2) 只读属性
class Circle:
def __init__(self, radius):
self.__radius = radius
@property
def radius(self): # 只读 getter
return self.__radius
@property
def area(self): # 动态计算属性
return 3.14 * self.__radius 2
c = Circle(5)
print(c.area) # 78.5
# c.radius = 10 # 报错!无 setter
(3) 延迟加载(Lazy Loading)
class Database:
def __init__(self):
self.__connection = None
@property
def connection(self):
if self.__connection is None:
self.__connection = self.__connect()
return self.__connection
def __connect(self):
print("建立数据库连接…")
return "Connection Object"
db = Database()
print(db.connection) # 第一次调用时初始化连接
五、注意事项
- 双下划线 __ 仅触发名称修饰(如 __attr 变为 _ClassName__attr),仍可强制访问,但约定禁止这样做。
- 简单数据类(如 DTO)可能不需要 getter/setter。
- 比 Java 风格的 get_xxx()/set_xxx() 更 Pythonic。
六、封装 vs 非封装对比
非封装示例(风险高)
class BankAccount:
def __init__(self, balance):
self.balance = balance # 直接暴露属性
account = BankAccount(1000)
account.balance = –500 # 非法操作,但无法阻止
封装示例(安全)
class BankAccount:
def __init__(self, balance):
self.__balance = balance
def deposit(self, amount):
if amount > 0:
self.__balance += amount
def withdraw(self, amount):
if 0 < amount <= self.__balance:
self.__balance -= amount
def get_balance(self):
return self.__balance
account = BankAccount(1000)
account.withdraw(500) # 必须通过方法操作
# account.__balance = -1000 # 无法直接修改
全文总结
封装是面向对象的核心原则,旨在隐藏实现细节、保护数据并简化使用。Python 通过单下划线“_”和双下划线“__”实现受保护与私有成员的命名约定,配合名称修饰机制防止直接访问;@property 装饰器将方法伪装成属性,在保持接口一致的同时加入校验、日志等逻辑;也可通过专门的公有方法强制所有操作都走统一入口。封装带来数据安全、低耦合、易维护与灵活性,常用于数据验证、只读属性、延迟加载等场景。但 Python 没有绝对私有,仍需开发者自律;简单数据类不宜过度封装。合理使用封装,可构建健壮、易维护的代码。
评论前必须登录!
注册