Python封装
困难6用 Python 给数据上把锁:封装入门
你有没有见过那种带锁的储蓄罐?只有投币口和取钱按钮,里面的硬币怎么放、有多少,别人看不见也碰不着。封装就像给代码里的数据做这样一个储蓄罐——把属性和方法“藏”在类里面,只通过几个公开的“小窗口”让别人用。这样一来,内部数据不会被人乱改,代码也更安全、更清晰。
封装是面向对象编程的三大特性之一(另外两个是继承和多态)。简单说,就是把东西打包好,只给外界必要的操作,里面怎么实现不关你的事。
1. 封装是什么?保护你的数据不“裸奔”
还是用玩具箱打比方:玩具箱外面只有一个小窗口、几个按钮,你按按钮就能拿到指定的玩具。至于箱子里怎么分门别类、玩具叠在哪里,你根本不用管。封装就是把类的属性(数据)和方法(操作)打包在一起,并且把一部分属性和方法藏起来,只开放安全的接口给别人调用。
- 私有(Private):藏在类内部,外部不能直接访问。
- 公开(Public):任何人(外部代码)都可以直接使用。
Python 里用两个下划线 __ 开头(但不是结尾)来表示私有,就像给属性贴了个“禁止触摸”的标签。
2. 为什么需要封装?三个生活小例子
例子1:手机电池(你已有的例子)
手机电量是内部数据,你只能通过“充电”慢慢加电,或者“打电话”消耗电量。如果允许直接把电量改成100%,那电池就乱套了——明明没电也能装成满格。封装后,别人只能通过公开方法(充电、打电话)来间接修改电量,规则牢牢攥在开发者手里。
例子2:游戏角色的血量
玩格斗游戏,你的角色血量(__hp)被隐藏了。打怪时只能调用 take_damage(10) 方法减少血量,回血只能调用 heal(20)。如果允许直接写 player.__hp = 0,那敌人可以一击秒杀,游戏就没意思了。封装保证了血量变化必须遵守游戏规则(比如不能小于0,不能大于上限)。
例子3:银行账户余额
你的银行卡余额是私有数据。你不能直接在 ATM 机上把余额头数字改成 1000 万,只能通过存款、取款、转账这些方法来操作,而且每次操作都有记录。封装保护了你的钱不被恶意修改。
总结:封装能防止数据被外部随意篡改,保证修改的“规则唯一”,同时隐藏复杂的实现细节,让别人用起来更简单。
3. Python 里怎么实现封装?用 __ 加上一双“隐形翅膀”
在属性或方法名字前面加两个下划线 __,它就变成了私有成员。外部代码直接写 对象.__属性 会报错。注意:这是“名称改写”(Name Mangling)——Python 在内部偷偷把名字改成了 _类名__属性,所以严格来说不是完全禁止访问,但强烈不推荐你绕过它。
基本写法
class Phone:
def __init__(self, brand):
self.__battery = 50 # 私有属性,初始电量 50%
self.brand = brand # 公有属性,可以直接访问
# 公开方法:打电话(每次减10%)
def call(self):
if self.__battery >= 10:
self.__battery -= 10
print(f"打电话中...剩余电量{self.__battery}%")
else:
print("电量不足,不能打电话")
# 公开方法:充电(每次加20%)
def charge(self):
self.__battery += 20
if self.__battery > 100:
self.__battery = 100
print(f"充电完成,当前电量{self.__battery}%")
# 公开方法:查看电量(只读)
def check_battery(self):
return self.__battery
# 使用
my_phone = Phone("小米")
print(my_phone.brand) # 可以读公有属性
my_phone.call() # 打电话,电量减10
my_phone.charge() # 充电,电量加20
print(my_phone.check_battery()) # 只能通过公开方法看电量
# 下面两行会报错
# print(my_phone.__battery) # AttributeError
# my_phone.__battery = 100 # 外部不能直接修改私有属性
小提示:名称改写是什么?
如果你非要“偷看”私有属性——记住,Python 其实把 __battery 改成了 _Phone__battery。你可以这样:
print(my_phone._Phone__battery) # 能访问,但千万别这样用!
这就像储蓄罐的小窗口其实有个暗门,但你用暗门是不对的,别人写代码看到会骂你 —— 因为破坏了封装。所以永远不要在外面用 _类名__属性 的方式访问私有成员,那是作弊。
4. 动手试一试:小猪存钱罐(完整代码)
现在你自己写一个小猪存钱罐类。要求:
- 私有属性
__money:存钱罐里的余额(初始0元) - 公开方法:
save(amount):存钱,金额必须是正数,存完打印“存了 xx 元,现在有 yy 元”take(amount):取钱,余额不够时打印提示;够的话就减少余额check_balance():返回余额(只读)
下面是参考答案:
class PiggyBank:
def __init__(self):
self.__money = 0 # 私有属性:余额初始0元
def save(self, amount):
"""存钱,amount必须是正数"""
if amount <= 0:
print("存钱金额必须大于0!")
return
self.__money += amount
print(f"存了{amount}元,现在有{self.__money}元")
def take(self, amount):
"""取钱,不能超过余额"""
if amount <= 0:
print("取钱金额必须大于0!")
return
if amount > self.__money:
print(f"余额不足,最多取{self.__money}元")
return
self.__money -= amount
print(f"取了{amount}元,剩下{self.__money}元")
def check_balance(self):
"""查看余额(只读)"""
return self.__money
# 使用存钱罐
pig = PiggyBank()
pig.save(100) # 存了100元,现在有100元
pig.save(50) # 存了50元,现在有150元
pig.take(30) # 取了30元,剩下120元
pig.take(200) # 余额不足,最多取120元
print(pig.check_balance()) # 120
# 不能这样改:
# pig.__money = 1000000 # AttributeError
5. 小心这些坑!新手常见错误
❌ 错误1:以为私有属性完全不能访问
实际上通过 _类名__属性 可以访问,但千万不要这样做。正确的做法是只使用类提供的公开方法。
❌ 错误2:忘记定义公开的“小窗口”方法
如果只把数据藏起来,却不提供任何访问或修改的方法,那就成了“死数据”。比如存钱罐没有 save() 方法,那这个罐子就废了。所以封装不是藏死,而是有管理地开放。
❌ 错误3:在类内部忘了用 self.__ 访问私有属性
如果你在类的方法里写成 self.__money 是可以的,但如果写成 __money(没有 self),Python 会认为你是在访问一个普通的局部变量,而不是私有属性,导致赋值无效或报错。
class Wrong:
def __init__(self):
self.__data = 10
def set(self, val):
__data = val # 错误!这里定义了局部变量__data,而不是修改私有属性
# 正确写法:self.__data = val
❌ 错误4:在外部修改私有属性后以为自己成功了
如果你写 obj.__attr = 100,Python 不会报错!但它并不是修改原来的私有属性,而是给对象添加了一个新的公有属性 __attr,和原来的私有属性完全无关。
class Test:
def __init__(self):
self.__x = 5
t = Test()
t.__x = 100 # 这不是修改私有属性,而是新建了一个公有属性
print(t.__x) # 输出100(这是新建的公有属性)
# 但依然可以通过 _Test__x 看到原来的私有属性是5
print(t._Test__x) # 5
所以不要被假象骗了 —— 私有属性一直安全地待在类里。
6. 完整代码示例:学生分数管理系统(综合运用)
把封装用在更贴近学校生活的例子里:老师管理学生成绩。
class Student:
def __init__(self, name, initial_score=0):
self.name = name # 姓名(公有)
self.__score = initial_score # 分数(私有)
self.__times = 0 # 修改次数(私有)
def set_score(self, new_score):
"""设置分数,只能由老师调用,且分数在0~100之间"""
if new_score < 0 or new_score > 100:
print("分数必须在0到100之间")
return
self.__score = new_score
self.__times += 1
print(f"已将{self.name}的分数改为{new_score}")
def get_score(self):
"""查看分数(只读)"""
return self.__score
def get_times(self):
"""查看分数被改过几次"""
return self.__times
# 使用
stu = Student("小明", 60)
print(f"{stu.name}起始分数:{stu.get_score()}") # 60
stu.set_score(85) # 改为85
stu.set_score(95) # 改为95
stu.set_score(120) # 错误提示
print(f"总共修改{stu.get_times()}次") # 2次
# stu.__score 无法直接访问
7. 接下来学什么?
封装只是面向对象的第一步。当你学会了封装,就可以去探索:
- 继承:子类可以继承父类的公有属性和方法,但不能直接访问父类的私有属性(只能通过父类公开方法间接使用)。
- 多态:不同类的对象可以对同一个公开方法做出不同的响应。
- property 装饰器:Python 提供的一种更优雅的封装方式,让你能把方法当属性用,既安全又简洁。
试试自己写一个“保险箱”类,用封装保护密码和存款,只有输入正确密码才能取钱。加油吧,小程序员!
例题精讲
在Python中,以下哪种方式定义的属性被视为私有属性,建议外部不要直接访问?
封装的主要目的是让代码更加易读和美观。
请补全以下代码,为Person类中的私有属性__age添加getter和setter方法,要求set_age方法中检查年龄必须大于0。
class Person:
def __init__(self, age):
self.__age = age
def get_age(self):
___①___
def set_age(self, age):
if age > 0:
___②___
else:
print("年龄必须大于0")关于Python中的封装,以下说法正确的是?
请补全以下BankAccount类中的deposit方法,要求存款金额必须大于0,并且增加存款到私有属性__balance中。
class BankAccount:
def __init__(self, balance):
self.__balance = balance
def deposit(self, amount):
if amount > 0:
___①___
else:
print("存款金额必须大于0")
def get_balance(self):
return self.__balance