欢迎来到 Python 学习计划的第 42 天!🎉
恭喜你!今天我们完成了 OOP 核心阶段(第 39-42 天) 的最后一块拼图。前几天我们学习了 继承(代码复用)和 多态(接口统一),今天我们将探讨 封装(Encapsulation)。
封装是保护数据安全的盾牌,它决定了哪些内部细节应该对外隐藏,哪些接口应该对外暴露。理解私有属性和名称修饰机制,是编写健壮、安全代码的关键!
一、什么是封装(Encapsulation)?
1. 定义
封装 是指将数据(属性)和操作数据的方法(行为)绑定在一起,并隐藏对象的内部实现细节,只对外提供公开的访问接口。
2. 为什么要封装?
- 数据安全:防止外部代码随意修改内部状态(如银行账户余额不能为负)。
- 降低复杂度:使用者只需关心接口,无需了解内部如何实现。
- 易于维护:内部实现改变不影响外部调用(只要接口不变)。
3. 比喻
- 手机:你只需要知道按哪个键打电话(公开接口),不需要知道内部电路如何连接(隐藏细节)。
- 胶囊:药物包裹在胶囊壳里,保护药物成分,控制释放。
二、访问控制:公有、保护与私有
Python 不像 Java 那样有严格的 public、protected、private 关键字,而是通过命名约定来实现访问控制。
修饰符 | 命名方式 | 访问权限 | 说明 |
|---|
公有 (Public) | var
| 任意访问 | 默认方式,内外均可访问 |
保护 (Protected) | _var
| 子类/内部 | 约定俗成,外部不应直接访问 |
私有 (Private) | __var
| 类内部 | 名称修饰,外部无法直接访问 |
1. 公有属性(默认)
任何人都可以访问和修改。
class Person: def __init__(self, name): self.name = name # 公有属性p = Person("Alice")p.name = "Bob" # ✅ 可以直接修改
2. 保护属性(单下划线)
约定俗成的私有,告诉开发者“这是内部用的,别在外面直接碰”。
class Person: def __init__(self, name): self._age = 18 # 保护属性p = Person("Alice")# p._age = 20 # ⚠️ 虽然能访问,但违反约定
3. 私有属性(双下划线)
强制私有。Python 会通过名称修饰(Name Mangling) 机制,让外部无法直接访问。
class BankAccount: def __init__(self, balance): self.__balance = balance # 私有属性acc = BankAccount(1000)# print(acc.__balance) # ❌ AttributeError: 无法直接访问
三、核心机制:名称修饰(Name Mangling)
1. 什么是名称修饰?
当类中出现 __var 形式的属性时,Python 解释器会自动将其重命名为 _ClassName__var。这是一种防止子类意外覆盖父类私有属性的机制。
2. 验证修饰效果
class Secret: def __init__(self): self.__password = "123456"s = Secret()# 直接访问失败# print(s.__password) # ❌ AttributeError# 查看内部字典print(s.__dict__) # 输出:{'_Secret__password': '123456'} (名字被改了!)# 强制访问(不推荐)print(s._Secret__password) # ✅ 能访问,但破坏了封装性
3. 为什么这样设计?
- 防止意外修改:避免外部代码随意更改关键数据。
- 避免子类冲突:父类的
__var 和子类的 __var 会被修饰成不同的名字,互不干扰。
class Parent: def __init__(self): self.__var = "Parent"class Child(Parent): def __init__(self): super().__init__() self.__var = "Child" # 实际是 _Child__varc = Child()# 两个 __var 共存,互不影响
四、安全访问私有属性:Getter 与 Setter
既然私有属性不能直接访问,如何读取或修改?答案是提供公开方法。
1. 传统方法(Java 风格)
class BankAccount: def __init__(self, balance): self.__balance = balance # Getter def get_balance(self): return self.__balance # Setter (带验证) def set_balance(self, amount): if amount < 0: raise ValueError("余额不能为负") self.__balance = amountacc = BankAccount(1000)print(acc.get_balance()) # 1000acc.set_balance(2000) # ✅ 安全修改# acc.set_balance(-100) # ❌ 抛出异常
2. Pythonic 方式:@property(预告)
传统写法太繁琐。Python 提供了 @property 装饰器,让方法像属性一样调用。
💡 注意:@property 的高级用法我们将在 第 46 天 深入讲解,今天先了解概念。
class BankAccount: def __init__(self, balance): self.__balance = balance @property def balance(self): return self.__balance @balance.setter def balance(self, amount): if amount < 0: raise ValueError("余额不能为负") self.__balance = amountacc = BankAccount(1000)print(acc.balance) # 像访问属性一样调用 getteracc.balance = 2000 # 像修改属性一样调用 setter
五、常见误区与注意事项
1. 私有不是绝对安全
名称修饰只是改了名字,有心人仍然可以通过 _ClassName__var 访问。Python 的私有是“君子协定”,主要防止意外修改,而非防黑客。
2. 不要滥用私有
- 过度封装:把所有属性都私有化,导致类难以使用。
- 建议:默认使用公有,只有在需要数据验证或防止误改时才用私有。
3. 子类无法直接访问父类私有属性
子类继承的是修饰后的名字,无法通过 __var 访问父类私有属性。
class Parent: def __init__(self): self.__var = "Parent"class Child(Parent): def show(self): # print(self.__var) # ❌ 报错,找不到 _Child__var print(self._Parent__var) # ✅ 可以但不推荐
4. 特殊方法例外
以双下划线开头和结尾的方法(如 __init__, __str__)不是私有方法,而是魔法方法,不会被修饰。
六、实战练习
练习 1:实现安全的用户类
创建一个 User 类,包含:
- 私有属性:
__password (密码) - 公有属性:
username (用户名) - 方法:
set_password() (设置密码,要求长度至少 6 位), check_password() (验证密码)
class User: def __init__(self, username, password): self.username = username self.__password = password def set_password(self, new_pwd): if len(new_pwd) < 6: raise ValueError("密码长度至少 6 位") self.__password = new_pwd def check_password(self, pwd): return self.__password == pwdu = User("alice", "123456")# u.set_password("123") # ❌ 报错u.set_password("654321") # ✅ 成功print(u.check_password("654321")) # True
练习 2:探索名称修饰
创建一个类,定义私有属性 __secret,实例化后查看 __dict__,验证属性名是否被修饰。
class MyClass: def __init__(self): self.__secret = "Hidden"obj = MyClass()print(obj.__dict__) # 输出包含 '_MyClass__secret': 'Hidden'# 验证访问# print(obj.__secret) # ❌ 报错print(obj._MyClass__secret) # ✅ 能访问(但不推荐)
七、总结
知识点 | 说明 |
|---|
封装 | 隐藏内部细节,提供公开接口,保护数据安全 |
公有属性 | var,任意访问,默认方式
|
保护属性 | _var,约定俗成,外部不应直接访问
|
私有属性 | __var,名称修饰,外部无法直接访问
|
名称修饰 | __var 自动变为 _ClassName__var
|
访问方式 | 通过公开方法(Getter/Setter)或 @property |
最佳实践 | 默认公有,需验证时用私有,避免过度封装 |
🎉 OOP 核心阶段完结!
恭喜你!完成了 第 39-42 天 的 OOP 三大特征学习。你已经掌握了:
- ✅ 继承:代码复用,建立层次关系
- ✅ 多态:同一接口,不同实现,鸭子类型
- ✅ 封装:隐藏细节,保护数据,私有属性