面向对象高级(封装)

我把车主姓名藏进了双下划线里|Python 面向对象高级(1):封装

系列说明:这是我第十一章「面向对象高级」的学习笔记,一共五篇,按 封装 → 继承与方法重写 → 多继承与 MRO → 多态与鸭子类型 → 综合实战 的顺序更新。这一篇是起点。

先说我在干嘛

第六章学类与对象时,我学会了怎么定义类、写 __init__ 魔法方法。到了第十一章,老师开篇直接甩出四个词:封装、继承、多态、鸭子类型。

我一开始的理解特别肤浅:封装,不就是"把代码写进类里"吗?

直到我写了这章的第一个例子------一辆车,然后被迫认真想一个问题:车主的名字,能让外面随便拿走吗?

封装到底是什么

我的理解很直白:给类里的成员分个阶级。

  • 公共的:类里面、类外面都能碰。

  • 私有的 :属性名前加两个下划线 __,类外面碰不到,只能内部自己用。

想通这点,剩下的就都是演示了。

完整代码

我把这一节的例子写成了 1.封装.py:

by tryer07 | Python 面向对象高级(1)封装 · 学习笔记

class Car:

公共属性 brand/model/color 类内外都能访问;

私有属性 __owner 名前加 __ ,类外无法直接访问

def init(self, brand, model, color, owner):

self.brand = brand

self.model = model

self.color = color

self.__owner = owner

公共方法 ------ start/run/stop 类内外都能调用

@staticmethod

def start():

print("汽车已经启动了")

def run(self):

私有成员在【类内部】可通过 self 正常访问

print(f"{self.__owner} 开着 {self.brand} {self.model} 在跑")

self.__control_fuel()

@staticmethod

def stop():

print("停下了")

私有方法:名前加 __ ,只能类内部使用

@staticmethod

def __control_fuel():

print("控制油门和刹车")

对外提供脱敏访问接口:类外通过它间接读取私有属性

def get_owner(self):

返回脱敏后的车主名:第一个字用 * 代替,例如 "外收内放" -> "*收内放"

return "*" + self.__owner1:

顺手补一个公共方法,下一篇讲继承时要用它演示"自动继承"和"重写"

def charge(self):

print(f"{self.brand}{self.model}正在补充燃料")

car = Car("奥迪", "A6", "黑色", "外收内放")

print("brand:", car.brand)

print("model:", car.model)

print("color:", car.color)

car.start()

car.run()

car.stop()

类外直接访问私有属性/私有方法 ------ 验证会报错

try:

print(getattr(car, "__owner"))

except AttributeError as e:

print("【报错验证】访问私有属性失败:", e)

try:

getattr(car, "__control_fuel")()

except AttributeError as e:

print("【报错验证】调用私有方法失败:", e)

通过公共方法获取脱敏后的车主信息

print("车主(脱敏):", car.get_owner())

用 _Car__xxx 强行访问私有成员 ------ 验证 Python 没有真正的私有

print("强行访问私有属性:", getattr(car, "_Car__owner"))

getattr(car, "_Car__control_fuel")()

跑出来的结果

brand: 奥迪

model: A6

color: 黑色

汽车已经启动了

外收内放 开着 奥迪 A6 在跑

控制油门和刹车

停下了

【报错验证】访问私有属性失败: 'Car' object has no attribute '__owner'

【报错验证】调用私有方法失败: 'Car' object has no attribute '__control_fuel'

车主(脱敏): *收内放

强行访问私有属性: 外收内放

控制油门和刹车

前面几行都符合预期:公共属性直接打印、公共方法直接调用,一点毛病没有。

有意思的其实是最后三行。

发现一:Python 的私有是"假的"

__owner 在类外面访问不到,报 AttributeError,看着挺像真正的私有。但我按 _类名__属性名 这个格式去够它一下:getattr(car, "_Car__owner") ------ 出来了,"外收内放"活得好好的。

后来才知道这叫名称改写(name mangling) :Python 解释器在编译阶段把类里的 __owner 悄悄改名成了 _Car__owner。所谓私有,只是"你按原名找不到",不是"真的拿不到"。

那这层伪装有什么意义?我想明白的答案是防撞名 。子类里也想定义一个跟父类同名的私有属性时,各自改写成 _父类名__xxx 和 _子类名__xxx,不会互相覆盖。至于防不防君子,Python 的态度很明确:你要硬够,我拦不住,但会留个难看的名头让你自己心虚。

发现二:脱敏接口才是封装的目的

如果只是"藏起来",那封装就太鸡肋了。真正有价值的是这个:

def get_owner(self):

return "*" + self.__owner1:

外面想知道车主是谁,可以,但只能拿到 *收内放。

这一行代码教会我一件事:私有属性不是为了拒绝外部访问,而是为了夺回"以什么形式被访问"的控制权。 密码、手机号、身份证、余额这种东西,我应该提供接口,而不是直接摆成公共属性。摆成公共属性之后,任何人在任何地方都能读、能改,出了事你连拦截点都找不到。

我还特意把父类的 charge() 也留成了公共方法,因为下一篇会用到它------一个子类"什么都没写"却能直接调用的方法,正好能演示继承到底白送了什么。

踩过的两个坑

坑一:car.__owner 这样写,PyCharm 会一路给你画红线。

我本来想用最直觉的方式演示"私有属性访问不了":

print(car.__owner) # 编辑器直接告警

它确实会抛 AttributeError,效果是对的,但在类外面写 __owner,IDE 判定你访问的是"当前作用域根本没有的名字",警告刷一片。所以我改成了 getattr(car, "__owner"):

try:

print(getattr(car, "__owner"))

except AttributeError as e:

print("【报错验证】访问私有属性失败:", e)

getattr 是按字符串动态查找,跟直接写属性名的行为完全一致,异常也一样,但检查器不会提前替你判定"这不可能存在"。演示"报错"这件事,重点是让 try/except 真的捕获到异常,而不是让编辑器先替我报错。

坑二:start() 和 stop() 明明在类里,我却写成了 @staticmethod。

PyCharm 会对它们提示 Method 'start' may be 'static'。因为它翻遍整个方法体,发现你一次都没用 self------既然不用实例上的任何东西,那它就不该占着实例方法的位置。

我一开始很抵触:方法写在类里凭什么不是实例方法?后来想通了,这个提示是有道理的。start() 只是打印一句固定文案,跟"哪一辆车"毫无关系,标成静态方法反而更诚实。而 run() 必须用 self.__owner、self.brand,所以它是老老实实的实例方法。

要不要 self,就是该不该静态的分界线。

相关推荐
全栈练习生43 分钟前
大模型原理之投机解码
python·ai
青少儿编程课堂44 分钟前
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:差分数组与区间修改实战解析
c++·python·算法·bfs·信息学竞赛
devilnumber1 小时前
Java 内部迭代 vs 外部迭代:从原理到实战避坑
java·开发语言
小小张说故事1 小时前
Python 的 sort 为什么又稳又快?拆开 Timsort:run 检测、minrun 与 galloping 的三板斧
python
学逆向的1 小时前
C printf原型解析
c语言·开发语言·网络安全
\光辉岁月/1 小时前
5.math类
java·开发语言
不像程序员的程序媛1 小时前
mybatisplus insert dto未指定id时会如何
java·开发语言
kaizq1 小时前
从照片开口说话实现到应用职业教培变革落地
python·应用职业教育·ai混元-元宝·数学学习基础·用中学-学中用·照片aigc-gif
OnlineProxy1 小时前
Python 高负载异步爬虫优化指南:深挖 httpx 与 aiohttp 的性能极限
爬虫·python·httpx