《Python 封装不是“锁门”,而是“贴标签”!深入 property、伪私有与 slots 的哲学》
一、开篇:面向对象的“家务活”
在 Python 的世界里,类就像一个家庭,属性就是家里的“物件”。
公有(public):客厅里的电视,谁来了都能按开关。
保护(protected):卧室里的衣柜,家人可以随便翻,外人进来只能看不能摸(单下划线
_)。私有(private):保险柜里的存折,只有主人(类内部)自己知道密码(双下划线
__)。
今天我们就来聊聊,如何用 Python 的“门禁系统”把这些物件管得明明白白。
二、基础篇:三大访问级别演义
class Person: def __init__(self, name, age, gender): self.name = name # 公有 self.__age = age # 私有(双下划线) self._gender = gender # 保护(单下划线) def __str__(self): return f"{self.name}, {self.__age}, {self._gender}" p = Person("张三", 30, "男") print(p.name) # ✅ 正常 print(p._gender) # ⚠️ 可以访问,但不建议(君子协定) # print(p.__age) # ❌ AttributeError: 找不到!三、进阶扩展①:撕开“私有”的画皮——Name Mangling(名称修饰)
Python 的私有其实是一层“皇帝的新衣”。它并没有真正锁死数据,只是偷偷改了个名字:
print(dir(p)) # 你会发现一个叫 _Person__age 的东西 print(p._Person__age) # 输出 30 (强制访问成功!)
核心结论:Python 的私有是“防君子不防小人”。它的目的是防止子类意外重写父类属性,而不是为了绝对安全。这也是 Python 社区的名言:“We are all consenting adults”(我们都是成年人,要为自己的行为负责)。
四、进阶扩展②:@property的“全家桶”(Get/Set/Delete)
光有私有属性还不够,我们得给外部提供优雅的访问通道。使用@property装饰器,可以让方法像属性一样调用。
class Person: def __init__(self, name): self.__name = name @property def name(self): print("正在读取姓名...") return self.__name @name.setter def name(self, value): if 2 <= len(value) <= 4: self.__name = value else: raise ValueError("名字长度必须在 2~4 之间") @name.deleter # 扩展:删除器 def name(self): print(f"删除姓名 {self.__name} 成功") self.__name = None p = Person("李四") p.name = "王五" # 调用 setter print(p.name) # 调用 getter del p.name # 调用 deleterdeleter 的应用场景:当删除属性时,需要释放外部资源(比如断开数据库连接、清除缓存),这时候 deleter 就派上了大用场。
五、进阶扩展③:保护变量(_)与子类继承的微妙关系
在父类中定义_gender,子类可以直接继承并使用它。但是!如果子类定义了__age(双下划线),它会被改写为_SuperPerson__age,完全独立于父类的_Person__age。这有效防止了继承链中的命名冲突。
六、进阶扩展④:性能大杀器__slots__
每个 Python 实例默认自带一个__dict__字典,用于存储属性。这在大量实例(比如游戏中成千上万个敌人)时会占用巨量内存。
如果你确定类只有固定的几个属性,加上__slots__瞬间降低内存占用:
class Person: __slots__ = ('__name', '__age') # 拒绝动态添加属性 def __init__(self, name, age): self.__name = name self.__age = age p = Person("张三", 30) # p.hobby = "coding" # ❌ AttributeError!不允许新增属性面试考点:__slots__会牺牲动态赋值的灵活性,换取更高的内存访问效率。在大型数据科学或游戏项目中,这是必备优化手段。
💡 总结金句
封装不是为了“隐藏”,而是为了控制。通过@property,我们让调用者感觉像在操作数据,但背后却在执行严谨的逻辑校验。这就是面向对象设计的精髓——接口简单,实现复杂。
