目 录CONTENT

文章目录

Python(二十八) 类的封装详解

Python(二十八) 类的封装详解

1. 什么是封装

封装是面向对象编程中的一个重要思想。

通俗地说:

封装就是把数据和操作数据的方法放在一个类里面,
并且控制外部代码如何访问和修改这些数据。

举一个生活中的例子:

手机把很多复杂零件封装在内部。
用户不用关心芯片、电池、电路板怎么工作。
用户只需要通过按钮、屏幕、充电口来使用手机。

在程序中也是一样。

一个对象内部可能有很多数据,但外部代码不应该随便乱改。

我们希望:

重要数据由类自己管理。
外部只能通过类提供的方法来访问和修改。

这就是封装。

2. 为什么需要封装

先看一个没有封装的例子。

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score


stu = Student("张三", 90)

stu.score = -100

print(stu.name)
print(stu.score)

输出:

张三
-100

问题出现了:

学生成绩被改成了 -100。
这明显不合理。

因为成绩通常应该在 0100 之间。

如果外部代码可以随便修改对象属性,就容易产生不合法的数据。

封装可以解决这个问题。

我们可以让外部不要直接修改成绩,而是通过方法修改。

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def update_score(self, new_score):
        if 0 <= new_score <= 100:
            self.score = new_score
        else:
            print("成绩必须在 0 到 100 之间")


stu = Student("张三", 90)

stu.update_score(95)
stu.update_score(-100)

print(stu.score)

输出:

成绩必须在 0 到 100 之间
95

这样就避免了不合理的数据进入对象。

3. 封装解决什么问题

封装主要解决三个问题。

3.1 让数据更安全

不让外部随便修改重要数据。

例如:

银行账户余额不能被随便改成负数。
学生成绩不能超过 100。
商品库存不能小于 0。

3.2 让代码更清晰

把数据和操作数据的方法放在一个类中。

例如:

BankAccount 类中放余额数据,也放存款和取款方法。
Student 类中放成绩数据,也放修改成绩和判断是否及格的方法。

3.3 让外部使用更简单

使用者不需要知道对象内部怎么实现,只需要知道可以调用哪些方法。

例如:

account.deposit(100)
account.withdraw(50)

外部只需要知道:

deposit() 可以存钱。
withdraw() 可以取钱。

不用关心余额在内部怎么保存。

4. 封装的核心思想

封装可以用一句话总结:

内部数据自己管,外部访问走方法。

也可以这样理解:

不该让外部直接碰的数据,就藏起来。
外部需要使用时,提供合适的方法。

封装不是为了把所有东西都藏起来。

封装是为了让数据访问更合理、更安全、更清晰。

5. Python 中的访问控制

在一些编程语言中,属性和方法有严格的访问权限,例如:

public
private
protected

Python 的访问控制没有那么严格。

Python 更强调约定。

常见命名方式:

写法 名称 含义
name 公开属性 外部可以直接访问
_name 受保护属性 约定内部使用,外部不建议直接访问
__name 私有属性 名称会被改写,外部不容易直接访问

示例:

class Student:
    def __init__(self):
        self.name = "张三"
        self._age = 18
        self.__score = 90

这里:

name 是公开属性。
_age 是受保护属性。
__score 是私有属性。

6. 公开属性

公开属性就是普通属性。

外部可以直接访问和修改。

class Student:
    def __init__(self, name):
        self.name = name


stu = Student("张三")

print(stu.name)

stu.name = "李四"
print(stu.name)

输出:

张三
李四

公开属性适合保存那些允许外部直接访问和修改的数据。

例如:

姓名
标题
颜色
普通状态

但是如果一个属性需要限制修改规则,就不适合完全公开。

7. 受保护属性

受保护属性通常以一个下划线 _ 开头。

例如:

class Student:
    def __init__(self, name, score):
        self.name = name
        self._score = score

这里的 _score 表示:

这个属性主要给类内部使用。
外部不建议直接访问。

注意:

一个下划线开头只是约定,不是强制限制。

也就是说,外部仍然可以访问。

class Student:
    def __init__(self, name, score):
        self.name = name
        self._score = score


stu = Student("张三", 90)

print(stu._score)

输出:

90

虽然可以访问,但不推荐。

教学时可以这样讲:

看到 _score,意思是作者提醒你:
这个属性是内部使用的,外部最好不要直接动。

8. 私有属性

私有属性以两个下划线 __ 开头。

示例:

class Student:
    def __init__(self, name, score):
        self.name = name
        self.__score = score


stu = Student("张三", 90)

print(stu.name)
print(stu.__score)

运行后:

print(stu.name) 可以正常输出。
print(stu.__score) 会报错。

因为 __score 是私有属性,外部不能直接用 对象.__score 访问。

私有属性适合保存不希望外部直接修改的数据。

例如:

账户余额
密码
内部状态
需要校验的数据

9. 私有属性的作用

私有属性的作用不是为了绝对安全,而是为了限制外部随意访问。

例如:

class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self.__balance = balance


account = BankAccount("张三", 1000)

print(account.owner)
print(account.__balance)

owner 可以访问。

__balance 不能直接访问。

这样做的目的是:

账户所有人可以公开。
账户余额不应该让外部随便改。

如果余额可以被随便改:

account.balance = -999999

就会产生严重问题。

10. 私有属性并不是绝对安全

Python 中的私有属性不是真正意义上的绝对隐藏。

Python 会把私有属性名做名称改写。

例如:

class Student:
    def __init__(self):
        self.__score = 90


stu = Student()

__score 在内部大致会被改写成:

_Student__score

所以 technically 可以这样访问:

print(stu._Student__score)

但是非常不推荐这样写。

教学时要强调:

双下划线不是保险箱。
它是一种名称保护机制。
正常代码不要绕过它。

11. 私有属性的正确访问方式

如果外部需要读取私有属性,应该提供公开方法。

例如:

class Student:
    def __init__(self, name, score):
        self.name = name
        self.__score = score

    def get_score(self):
        return self.__score


stu = Student("张三", 90)

print(stu.get_score())

输出:

90

这里:

__score 是私有属性。
get_score() 是公开方法。
外部通过 get_score() 读取成绩。

12. 私有属性的正确修改方式

如果外部需要修改私有属性,也应该提供方法。

示例:

class Student:
    def __init__(self, name, score):
        self.name = name
        self.__score = score

    def get_score(self):
        return self.__score

    def set_score(self, score):
        if 0 <= score <= 100:
            self.__score = score
        else:
            print("成绩必须在 0 到 100 之间")


stu = Student("张三", 90)

stu.set_score(95)
print(stu.get_score())

stu.set_score(-10)
print(stu.get_score())

输出:

95
成绩必须在 0 到 100 之间
95

这样做的好处是:

外部不能随便乱改成绩。
必须通过 set_score() 方法。
set_score() 可以检查数据是否合法。

13. getter 和 setter

在面向对象中,读取属性的方法通常叫 getter。

修改属性的方法通常叫 setter。

例如:

def get_score(self):
    return self.__score

这是 getter。

def set_score(self, score):
    self.__score = score

这是 setter。

完整示例:

class Student:
    def __init__(self, score):
        self.__score = score

    def get_score(self):
        return self.__score

    def set_score(self, score):
        if 0 <= score <= 100:
            self.__score = score
        else:
            print("成绩不合法")


stu = Student(80)

print(stu.get_score())

stu.set_score(90)
print(stu.get_score())

输出:

80
90

教学中可以这样讲:

getter 用来读。
setter 用来改。
改的时候可以顺便做检查。

14. 使用 property

getter 和 setter 很清楚,但调用起来稍微有点麻烦。

stu.get_score()
stu.set_score(90)

Python 提供了 property,可以让方法像属性一样使用。

示例:

class Student:
    def __init__(self, score):
        self.__score = score

    @property
    def score(self):
        return self.__score

    @score.setter
    def score(self, value):
        if 0 <= value <= 100:
            self.__score = value
        else:
            print("成绩必须在 0 到 100 之间")


stu = Student(80)

print(stu.score)

stu.score = 95
print(stu.score)

stu.score = 150
print(stu.score)

输出:

80
95
成绩必须在 0 到 100 之间
95

这里看起来像直接访问属性:

stu.score

其实背后调用的是方法。

15. property 的好处

property 的好处是:

外部使用起来像普通属性。
内部仍然可以做数据校验和控制。

例如:

stu.score = 95

看起来像直接赋值。

但实际上会调用:

@score.setter
def score(self, value):
    ...

这就实现了:

外部用法简单。
内部控制严格。

16. 只读属性

如果只写 @property,不写 setter,那么这个属性就是只读的。

示例:

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return 3.14 * self.radius * self.radius


circle = Circle(5)

print(circle.area)

输出:

78.5

如果尝试修改:

circle.area = 100

会报错。

因为 area 是根据 radius 计算出来的,不应该直接修改。

这种场景很适合使用只读属性。

教学时可以这样说:

能读但不能改,就是只读属性。

17. 计算属性

有些属性并不是直接保存的,而是根据其他属性计算出来的。

例如矩形面积:

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    @property
    def area(self):
        return self.width * self.height


rect = Rectangle(3, 4)

print(rect.area)

输出:

12

这里 area 没有直接保存。

它是通过:

self.width * self.height

计算出来的。

这种方式可以保证:

只要 width 或 height 变化,area 也会自动得到新的计算结果。

18. 私有方法

除了私有属性,也可以定义私有方法。

私有方法以两个下划线 __ 开头。

示例:

class Student:
    def __init__(self, name, score):
        self.name = name
        self.__score = score

    def __check_score(self, score):
        return 0 <= score <= 100

    def set_score(self, score):
        if self.__check_score(score):
            self.__score = score
        else:
            print("成绩不合法")

    def get_score(self):
        return self.__score


stu = Student("张三", 80)

stu.set_score(90)
print(stu.get_score())

输出:

90

这里:

__check_score() 是私有方法。
它只给类内部使用。
外部不需要直接调用。

如果外部这样调用:

stu.__check_score(90)

会报错。

19. 私有方法适合放什么

私有方法通常用于类内部的小步骤。

例如:

检查成绩是否合法
格式化数据
计算内部结果
校验密码格式
处理内部状态

示例:

class User:
    def __init__(self, username, password):
        self.username = username
        self.__password = password

    def __check_password_length(self, password):
        return len(password) >= 6

    def change_password(self, new_password):
        if self.__check_password_length(new_password):
            self.__password = new_password
            print("密码修改成功")
        else:
            print("密码长度不能少于 6 位")


user = User("alice", "123456")

user.change_password("abc")
user.change_password("abcdef")

输出:

密码长度不能少于 6 位
密码修改成功

20. 封装中的公开接口

封装后,外部代码通过类提供的公开方法来使用对象。

这些公开方法可以理解为对象的“接口”。

例如:

class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self.__balance = balance

    def deposit(self, money):
        if money > 0:
            self.__balance += money
        else:
            print("存款金额必须大于 0")

    def withdraw(self, money):
        if money <= 0:
            print("取款金额必须大于 0")
        elif money > self.__balance:
            print("余额不足")
        else:
            self.__balance -= money

    def get_balance(self):
        return self.__balance

外部使用:

account = BankAccount("张三", 1000)

account.deposit(500)
account.withdraw(200)

print(account.get_balance())

外部只需要知道:

deposit() 存钱。
withdraw() 取钱。
get_balance() 查看余额。

不需要知道余额在内部如何保存。

这就是封装带来的好处。

21. 银行账户案例

下面是一个比较完整的封装案例。

class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.__balance = balance

    def deposit(self, money):
        if money <= 0:
            print("存款金额必须大于 0")
            return

        self.__balance += money
        print(f"成功存入 {money} 元")

    def withdraw(self, money):
        if money <= 0:
            print("取款金额必须大于 0")
            return

        if money > self.__balance:
            print("余额不足")
            return

        self.__balance -= money
        print(f"成功取出 {money} 元")

    def get_balance(self):
        return self.__balance


account = BankAccount("张三", 1000)

account.deposit(500)
account.withdraw(300)
account.withdraw(2000)

print(account.get_balance())

输出:

成功存入 500 元
成功取出 300 元
余额不足
1200

这个类中:

owner 是公开属性。
__balance 是私有属性。
deposit() 是公开方法。
withdraw() 是公开方法。
get_balance() 是公开方法。

为什么 __balance 要私有?

因为余额不能让外部随便改。
必须通过存款和取款方法按规则修改。

22. 学生成绩案例

class Student:
    def __init__(self, name, score):
        self.name = name
        self.__score = 0
        self.set_score(score)

    def set_score(self, score):
        if 0 <= score <= 100:
            self.__score = score
        else:
            print("成绩必须在 0 到 100 之间")

    def get_score(self):
        return self.__score

    def is_passed(self):
        return self.__score >= 60


stu = Student("张三", 90)

print(stu.get_score())
print(stu.is_passed())

stu.set_score(150)
print(stu.get_score())

输出:

90
True
成绩必须在 0 到 100 之间
90

这里有一个细节:

self.__score = 0
self.set_score(score)

先给 __score 一个默认值,然后通过 set_score() 设置真实成绩。

这样创建对象时也会进行成绩校验。

23. 使用 property 改写学生成绩案例

class Student:
    def __init__(self, name, score):
        self.name = name
        self.__score = 0
        self.score = score

    @property
    def score(self):
        return self.__score

    @score.setter
    def score(self, value):
        if 0 <= value <= 100:
            self.__score = value
        else:
            print("成绩必须在 0 到 100 之间")

    def is_passed(self):
        return self.__score >= 60


stu = Student("张三", 90)

print(stu.score)
print(stu.is_passed())

stu.score = 150
print(stu.score)

stu.score = 95
print(stu.score)

输出:

90
True
成绩必须在 0 到 100 之间
90
95

这个版本中:

外部使用 stu.score,看起来像普通属性。
内部实际通过 property 控制读写。

24. 商品库存案例

商品库存不能小于 0

可以使用封装来控制。

class Product:
    def __init__(self, name, price, stock):
        self.name = name
        self.price = price
        self.__stock = 0
        self.add_stock(stock)

    def add_stock(self, count):
        if count > 0:
            self.__stock += count
        else:
            print("增加库存数量必须大于 0")

    def sell(self, count):
        if count <= 0:
            print("销售数量必须大于 0")
        elif count > self.__stock:
            print("库存不足")
        else:
            self.__stock -= count
            print(f"成功卖出 {count} 件")

    def get_stock(self):
        return self.__stock


product = Product("苹果", 5, 100)

product.sell(3)
product.sell(200)
product.add_stock(50)

print(product.get_stock())

输出:

成功卖出 3 件
库存不足
147

这里:

库存通过 __stock 私有保存。
外部不能直接乱改库存。
增加库存用 add_stock()。
销售商品用 sell()。
查看库存用 get_stock()。

25. 用户密码案例

密码不应该随便被外部读取。

可以这样设计:

class User:
    def __init__(self, username, password):
        self.username = username
        self.__password = password

    def check_password(self, password):
        return self.__password == password

    def change_password(self, old_password, new_password):
        if not self.check_password(old_password):
            print("原密码错误")
            return

        if len(new_password) < 6:
            print("新密码长度不能少于 6 位")
            return

        self.__password = new_password
        print("密码修改成功")


user = User("alice", "123456")

print(user.check_password("111111"))
print(user.check_password("123456"))

user.change_password("111111", "abcdef")
user.change_password("123456", "abc")
user.change_password("123456", "abcdef")

输出:

False
True
原密码错误
新密码长度不能少于 6 位
密码修改成功

这个案例中:

密码是私有属性。
外部不能直接读取密码。
只能检查密码是否正确,或按规则修改密码。

26. 封装和普通属性的选择

不是所有属性都必须写成私有属性。

如果属性很普通,外部直接访问没有风险,可以使用公开属性。

例如:

class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author

这里 titleauthor 公开通常没问题。

如果属性需要校验,或者不希望外部直接修改,就可以封装。

例如:

成绩
余额
库存
密码
年龄
价格

这些数据通常需要规则限制。

教学总结:

能随便读写的数据,可以公开。
需要规则控制的数据,适合封装。

27. 封装不是越多越好

初学者容易产生一种误解:

所有属性都必须写成 __私有属性。

这不是必须的。

封装的目的不是让代码变复杂。

封装的目的是:

保护重要数据。
隐藏内部细节。
让外部使用更清晰。

如果一个属性没有复杂规则,公开使用也可以。

例如:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

对于简单坐标点,直接公开 xy 通常很自然。

28. 封装中的命名习惯

常见命名建议:

类型 示例 说明
公开属性 name 外部可以直接访问
受保护属性 _score 约定内部使用
私有属性 __balance 外部不应直接访问
公开方法 deposit() 给外部调用
私有方法 __check_money() 类内部使用

示例:

class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self.__balance = balance

    def __check_money(self, money):
        return money > 0

    def deposit(self, money):
        if self.__check_money(money):
            self.__balance += money

这里:

owner 公开。
__balance 私有。
__check_money() 私有。
deposit() 公开。

29. 单下划线和双下划线的区别

29.1 单下划线

self._score = score

含义:

约定内部使用。
外部不建议访问。
但语法上可以访问。

示例:

class Student:
    def __init__(self):
        self._score = 90


stu = Student()
print(stu._score)

可以输出:

90

29.2 双下划线

self.__score = score

含义:

Python 会进行名称改写。
外部不能直接用 stu.__score 访问。

示例:

class Student:
    def __init__(self):
        self.__score = 90


stu = Student()
print(stu.__score)

会报错。

教学总结:

_name 是提醒别人不要直接用。
__name 是更强一点的限制。

30. 封装与数据校验

封装最常见的用途之一,就是数据校验。

例如年龄不能为负数。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.__age = 0
        self.set_age(age)

    def set_age(self, age):
        if 0 <= age <= 150:
            self.__age = age
        else:
            print("年龄必须在 0 到 150 之间")

    def get_age(self):
        return self.__age


person = Person("张三", 18)

print(person.get_age())

person.set_age(-5)
print(person.get_age())

输出:

18
年龄必须在 0 到 150 之间
18

如果没有封装:

person.age = -5

就可能让对象进入不合理状态。

31. 封装与隐藏实现细节

封装不仅是为了保护数据,也可以隐藏实现细节。

例如,订单金额的计算可能比较复杂。

外部只需要调用:

order.total_price()

不需要知道内部如何计算。

示例:

class Order:
    def __init__(self, price, count, discount):
        self.price = price
        self.count = count
        self.discount = discount

    def total_price(self):
        original = self.price * self.count
        return original * self.discount


order = Order(100, 3, 0.8)

print(order.total_price())

输出:

240.0

外部只关心结果,不关心计算细节。

这也是封装。

32. 封装与修改内部实现

封装还有一个好处:

内部实现可以改变,但外部使用方式尽量不变。

例如,原来余额直接保存在 __balance 中:

def get_balance(self):
    return self.__balance

后来想增加手续费、冻结金额等内部逻辑。

只要 get_balance() 的使用方式不变,外部代码就不需要大改。

这就是封装的价值:

外部依赖公开方法。
内部实现可以逐步调整。

33. 常见错误 1:以为双下划线是绝对安全

错误理解:

__password 写成私有后,别人绝对访问不到。

实际情况:

Python 的私有属性只是名称改写,不是绝对安全机制。

不要把敏感信息的安全完全依赖于双下划线。

教学中可以这样讲:

双下划线是提醒和保护,不是保险箱。

34. 常见错误 2:私有属性外部直接访问

错误写法:

class Student:
    def __init__(self):
        self.__score = 90


stu = Student()
print(stu.__score)

会报错。

正确做法:

class Student:
    def __init__(self):
        self.__score = 90

    def get_score(self):
        return self.__score


stu = Student()
print(stu.get_score())

35. 常见错误 3:setter 中忘记校验

不推荐:

class Student:
    def __init__(self):
        self.__score = 0

    def set_score(self, score):
        self.__score = score

这样写虽然用了私有属性,但没有起到保护作用。

更推荐:

class Student:
    def __init__(self):
        self.__score = 0

    def set_score(self, score):
        if 0 <= score <= 100:
            self.__score = score
        else:
            print("成绩不合法")

封装的重点不只是“藏起来”,还要“按规则访问”。

36. 常见错误 4:在 init 中绕过校验

错误写法:

class Student:
    def __init__(self, score):
        self.__score = score

    def set_score(self, score):
        if 0 <= score <= 100:
            self.__score = score

这样创建对象时:

stu = Student(999)

仍然可以得到不合法成绩。

更推荐:

class Student:
    def __init__(self, score):
        self.__score = 0
        self.set_score(score)

    def set_score(self, score):
        if 0 <= score <= 100:
            self.__score = score
        else:
            print("成绩不合法")

这样创建对象时也会校验数据。

37. 常见错误 5:property 名称和私有属性名混乱

推荐写法:

class Student:
    def __init__(self, score):
        self.__score = score

    @property
    def score(self):
        return self.__score

    @score.setter
    def score(self, value):
        self.__score = value

不推荐把公开属性和私有属性混在一起乱命名。

例如:

self.score = score

同时又写:

@property
def score(self):
    return self.score

这种写法可能导致递归错误。

错误示例:

class Student:
    @property
    def score(self):
        return self.score

这里 return self.score 会再次调用 score 属性,容易无限递归。

正确写法:

class Student:
    def __init__(self, score):
        self.__score = score

    @property
    def score(self):
        return self.__score

38. 常见错误 6:把所有方法都写成私有

不推荐:

class Student:
    def __show_info(self):
        print("学生信息")

如果外部需要调用这个方法,就不应该写成私有方法。

私有方法适合类内部使用。

公开方法适合外部调用。

判断方法:

外部需要用,就公开。
只给内部辅助用,就私有。

39. 常见错误 7:过度封装

有些初学者会把每个属性都写成:

__name
get_name()
set_name()

如果这个属性没有任何校验规则,也没有隐藏必要,就会让代码变得啰嗦。

例如:

class Book:
    def __init__(self, title):
        self.__title = title

    def get_title(self):
        return self.__title

    def set_title(self, title):
        self.__title = title

如果 title 只是普通书名,直接公开也可以:

class Book:
    def __init__(self, title):
        self.title = title

教学中可以强调:

封装是为了清晰和安全,不是为了把简单问题复杂化。

40. 常见错误 8:通过名称改写强行访问私有属性

虽然可以这样访问:

stu._Student__score

但是不推荐。

原因:

这破坏了类的封装。
代码依赖了类的内部实现。
以后类名或属性名变化,外部代码容易出问题。

正确做法:

stu.get_score()

或者使用:

stu.score

前提是类中定义了 property

41. 注意事项 1:明确哪些数据需要保护

不是所有数据都需要私有化。

需要保护的数据通常有:

必须满足范围的数据,比如成绩、年龄、价格
不能随便修改的数据,比如余额、库存
不希望外部知道的数据,比如密码
内部计算使用的数据,比如缓存、状态标记

普通数据可以公开。

例如:

姓名
标题
描述
颜色

是否私有,取决于这个数据是否需要控制访问。

42. 注意事项 2:公开方法要名字清楚

公开方法是给外部使用的,所以名字应该清楚。

推荐:

deposit()
withdraw()
get_balance()
set_score()
change_password()

不推荐:

do()
handle()
change()
process()

方法名越清楚,越容易教学和维护。

43. 注意事项 3:私有方法只做内部辅助

私有方法通常不应该承担外部业务入口。

例如:

def __check_score(self, score):
    return 0 <= score <= 100

这是合理的内部辅助方法。

外部真正调用的是:

def set_score(self, score):
    ...

44. 注意事项 4:封装要配合清晰的类设计

封装不是孤立的。

一个类应该有清楚的职责。

例如:

Student 管理学生信息和学生相关行为。
BankAccount 管理账户信息和存取款行为。
Product 管理商品信息和库存行为。

不要把很多不相关的功能都塞进一个类。

例如:

Student 类里同时处理银行余额、商品库存、文件下载。

这样会让封装失去意义。

45. 注意事项 5:封装后外部应该少依赖内部细节

如果一个属性是私有的,外部就不应该依赖它的具体名字。

例如,不应该写:

account._BankAccount__balance

应该写:

account.get_balance()

这样即使类内部以后改成:

self.__money

只要 get_balance() 还在,外部代码就不需要改。

46. 注意事项 6:属性校验要考虑边界值

例如成绩:

0 分是否合法?
100 分是否合法?
-1 分是否合法?
101 分是否合法?

通常:

0 <= score <= 100

表示 0100 都合法。

商品数量:

count > 0

表示必须大于 0

边界条件是教学中很好的练习点。

47. 注意事项 7:setter 不一定必须返回值

很多 setter 方法只负责修改属性,不需要返回值。

例如:

def set_score(self, score):
    if 0 <= score <= 100:
        self.__score = score
    else:
        print("成绩不合法")

如果需要告诉外部修改是否成功,也可以返回布尔值。

def set_score(self, score):
    if 0 <= score <= 100:
        self.__score = score
        return True
    return False

两种写法都可以,教学时根据课程阶段选择。

48. 注意事项 8:print 和 return 要区分

方法中经常会用到 printreturn

def get_score(self):
    return self.__score

return 是把结果交给调用者。

def show_score(self):
    print(self.__score)

print 是直接打印结果。

如果方法名是 get_score(),通常应该使用 return

如果方法名是 show_score(),通常可以使用 print

49. 综合案例:封装版学生类

class Student:
    def __init__(self, name, age, score):
        self.name = name
        self.__age = 0
        self.__score = 0
        self.set_age(age)
        self.set_score(score)

    def set_age(self, age):
        if 0 <= age <= 150:
            self.__age = age
        else:
            print("年龄必须在 0 到 150 之间")

    def get_age(self):
        return self.__age

    def set_score(self, score):
        if 0 <= score <= 100:
            self.__score = score
        else:
            print("成绩必须在 0 到 100 之间")

    def get_score(self):
        return self.__score

    def is_passed(self):
        return self.__score >= 60

    def show_info(self):
        print(f"姓名:{self.name},年龄:{self.__age},成绩:{self.__score}")


stu = Student("张三", 18, 90)

stu.show_info()

stu.set_age(-5)
stu.set_score(200)

stu.show_info()
print(stu.is_passed())

输出:

姓名:张三,年龄:18,成绩:90
年龄必须在 0 到 150 之间
成绩必须在 0 到 100 之间
姓名:张三,年龄:18,成绩:90
True

这个案例中:

name 是公开属性。
__age 是私有属性。
__score 是私有属性。
set_age() 和 set_score() 负责修改并校验数据。
get_age() 和 get_score() 负责读取数据。

50. 综合案例:封装版矩形类

class Rectangle:
    def __init__(self, width, height):
        self.__width = 0
        self.__height = 0
        self.width = width
        self.height = height

    @property
    def width(self):
        return self.__width

    @width.setter
    def width(self, value):
        if value > 0:
            self.__width = value
        else:
            print("宽度必须大于 0")

    @property
    def height(self):
        return self.__height

    @height.setter
    def height(self, value):
        if value > 0:
            self.__height = value
        else:
            print("高度必须大于 0")

    @property
    def area(self):
        return self.__width * self.__height

    @property
    def perimeter(self):
        return (self.__width + self.__height) * 2


rect = Rectangle(3, 4)

print(rect.width)
print(rect.height)
print(rect.area)
print(rect.perimeter)

rect.width = -10
print(rect.area)

输出:

3
4
12
14
宽度必须大于 0
12

这个案例适合讲:

property 可以控制属性读写。
area 和 perimeter 是只读计算属性。

51. 课堂练习 1:封装学生成绩

要求:

  1. 定义 Student 类。
  2. 有公开属性 name
  3. 有私有属性 __score
  4. 提供 set_score() 修改成绩。
  5. 提供 get_score() 查看成绩。
  6. 成绩必须在 0100 之间。

参考答案:

class Student:
    def __init__(self, name, score):
        self.name = name
        self.__score = 0
        self.set_score(score)

    def set_score(self, score):
        if 0 <= score <= 100:
            self.__score = score
        else:
            print("成绩不合法")

    def get_score(self):
        return self.__score


stu = Student("张三", 90)
print(stu.get_score())

stu.set_score(120)
print(stu.get_score())

52. 课堂练习 2:封装银行账户

要求:

  1. 定义 BankAccount 类。
  2. 有公开属性 owner
  3. 有私有属性 __balance
  4. 提供 deposit() 方法存款。
  5. 提供 withdraw() 方法取款。
  6. 提供 get_balance() 方法查看余额。
  7. 取款金额不能超过余额。

参考答案:

class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.__balance = balance

    def deposit(self, money):
        if money > 0:
            self.__balance += money
        else:
            print("存款金额必须大于 0")

    def withdraw(self, money):
        if money <= 0:
            print("取款金额必须大于 0")
        elif money > self.__balance:
            print("余额不足")
        else:
            self.__balance -= money

    def get_balance(self):
        return self.__balance


account = BankAccount("张三", 1000)
account.deposit(500)
account.withdraw(300)
print(account.get_balance())

53. 课堂练习 3:使用 property 封装年龄

要求:

  1. 定义 Person 类。
  2. 有私有属性 __age
  3. 使用 property 让外部通过 person.age 读取年龄。
  4. 使用 setter 限制年龄必须在 0150 之间。

参考答案:

class Person:
    def __init__(self, age):
        self.__age = 0
        self.age = age

    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, value):
        if 0 <= value <= 150:
            self.__age = value
        else:
            print("年龄不合法")


person = Person(18)
print(person.age)

person.age = -5
print(person.age)

54. 课堂练习 4:只读面积属性

要求:

  1. 定义 Circle 类。
  2. radius 属性。
  3. 使用 @property 定义只读属性 area
  4. area 返回圆的面积。

参考答案:

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return 3.14 * self.radius * self.radius


circle = Circle(5)
print(circle.area)

55. 课堂练习 5:封装商品库存

要求:

  1. 定义 Product 类。
  2. 有公开属性 name
  3. 有私有属性 __stock
  4. 提供 add_stock() 增加库存。
  5. 提供 sell() 销售商品。
  6. 提供 get_stock() 查看库存。
  7. 库存不能小于 0

参考答案:

class Product:
    def __init__(self, name, stock):
        self.name = name
        self.__stock = 0
        self.add_stock(stock)

    def add_stock(self, count):
        if count > 0:
            self.__stock += count
        else:
            print("增加库存数量必须大于 0")

    def sell(self, count):
        if count <= 0:
            print("销售数量必须大于 0")
        elif count > self.__stock:
            print("库存不足")
        else:
            self.__stock -= count

    def get_stock(self):
        return self.__stock


product = Product("苹果", 100)
product.sell(3)
print(product.get_stock())

56. 课堂练习 6:判断代码错误

下面代码有什么问题?

class Student:
    def __init__(self):
        self.__score = 90


stu = Student()
print(stu.__score)

参考答案:

__score 是私有属性,不能在类外通过 stu.__score 直接访问。
应该提供 get_score() 方法,或使用 property。

57. 课堂练习 7:判断是否需要封装

下面哪些属性更适合封装?

1. 学生姓名
2. 学生成绩
3. 银行账户余额
4. 商品库存
5. 文章标题
6. 用户密码

参考答案:

更适合封装:
2. 学生成绩
3. 银行账户余额
4. 商品库存
6. 用户密码

可以公开:
1. 学生姓名
5. 文章标题

原因:

成绩、余额、库存、密码需要访问控制或数据校验。
姓名、标题通常可以直接访问和修改。

58. 课堂练习 8:补全代码

补全代码,让价格不能小于 0

题目:

class Product:
    def __init__(self, price):
        self.__price = 0
        self.set_price(price)

    def set_price(self, price):
        # 请补全这里

    def get_price(self):
        return self.__price

参考答案:

class Product:
    def __init__(self, price):
        self.__price = 0
        self.set_price(price)

    def set_price(self, price):
        if price >= 0:
            self.__price = price
        else:
            print("价格不能小于 0")

    def get_price(self):
        return self.__price

59. 课堂练习 9:把 getter/setter 改成 property

原代码:

class Student:
    def __init__(self, score):
        self.__score = score

    def get_score(self):
        return self.__score

    def set_score(self, score):
        if 0 <= score <= 100:
            self.__score = score

请改成 property 写法。

参考答案:

class Student:
    def __init__(self, score):
        self.__score = 0
        self.score = score

    @property
    def score(self):
        return self.__score

    @score.setter
    def score(self, value):
        if 0 <= value <= 100:
            self.__score = value
        else:
            print("成绩不合法")

60. 课堂练习 10:设计封装类

要求设计一个 Temperature 类:

  1. 私有属性 __celsius 表示摄氏温度。
  2. 使用 property 读取和修改摄氏温度。
  3. 摄氏温度不能低于 -273.15
  4. 提供只读属性 fahrenheit,返回华氏温度。

参考答案:

class Temperature:
    def __init__(self, celsius):
        self.__celsius = 0
        self.celsius = celsius

    @property
    def celsius(self):
        return self.__celsius

    @celsius.setter
    def celsius(self, value):
        if value >= -273.15:
            self.__celsius = value
        else:
            print("温度不能低于绝对零度")

    @property
    def fahrenheit(self):
        return self.__celsius * 9 / 5 + 32


temp = Temperature(25)
print(temp.celsius)
print(temp.fahrenheit)

temp.celsius = -300
print(temp.celsius)

61. 教学总结

封装是 Python 面向对象编程中的重要思想。

一句话总结:

封装就是把数据和操作数据的方法放在类中,
并控制外部如何访问和修改这些数据。

核心知识:

  1. 公开属性可以直接访问。
  2. _属性名 表示受保护属性,约定内部使用。
  3. __属性名 表示私有属性,外部不能直接访问。
  4. 私有属性通常通过公开方法访问。
  5. getter 用来读取数据。
  6. setter 用来修改数据。
  7. setter 中常常进行数据校验。
  8. property 可以让方法像属性一样使用。
  9. 私有方法适合类内部辅助逻辑。
  10. 封装不是越多越好,要根据数据是否需要保护来决定。

常用代码模板:

class 类名:
    def __init__(self, value):
        self.__value = value

    def get_value(self):
        return self.__value

    def set_value(self, value):
        if 条件:
            self.__value = value
        else:
            print("数据不合法")

property 模板:

class 类名:
    def __init__(self, value):
        self.__value = value

    @property
    def value(self):
        return self.__value

    @value.setter
    def value(self, new_value):
        if 条件:
            self.__value = new_value
        else:
            print("数据不合法")

课堂记忆口诀:

公开属性随便用,
单下划线别乱动,
双下划线藏内部,
读取修改走方法。
getter 负责读,
setter 负责改,
property 更像属性,
校验数据更安全。

最后记住:

封装不是为了把代码藏起来,
而是为了让数据更安全,让使用更简单,让代码更清楚。
0
博主关闭了当前页面的评论