我把车主姓名藏进了双下划线里|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,就是该不该静态的分界线。