CC++ & Algorithm

Python封装

困难6
语言版本:C++Python
概述:封装就是把东西藏进盒子里,只留小窗口让别人用,保护内部数据不被乱动。

用 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 提供的一种更优雅的封装方式,让你能把方法当属性用,既安全又简洁。

试试自己写一个“保险箱”类,用封装保护密码和存款,只有输入正确密码才能取钱。加油吧,小程序员!

例题精讲

1单选题

在Python中,以下哪种方式定义的属性被视为私有属性,建议外部不要直接访问?

Aname
B_name
C__name
D__name__
2判断题

封装的主要目的是让代码更加易读和美观。

3填空题
请补全以下代码,为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")
4单选题

关于Python中的封装,以下说法正确的是?

A私有属性只能在类内部被访问,包括子类也无法访问。
B使用单下划线前缀(_xxx)的属性是严格的私有属性。
C利用@property装饰器可以实现优雅的访问控制,模拟属性的访问行为。
DPython的封装是绝对安全的,无法通过任何方式绕过。
5填空题
请补全以下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