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。
这明显不合理。
因为成绩通常应该在 0 到 100 之间。
如果外部代码可以随便修改对象属性,就容易产生不合法的数据。
封装可以解决这个问题。
我们可以让外部不要直接修改成绩,而是通过方法修改。
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
这里 title 和 author 公开通常没问题。
如果属性需要校验,或者不希望外部直接修改,就可以封装。
例如:
成绩
余额
库存
密码
年龄
价格
这些数据通常需要规则限制。
教学总结:
能随便读写的数据,可以公开。
需要规则控制的数据,适合封装。
27. 封装不是越多越好
初学者容易产生一种误解:
所有属性都必须写成 __私有属性。
这不是必须的。
封装的目的不是让代码变复杂。
封装的目的是:
保护重要数据。
隐藏内部细节。
让外部使用更清晰。
如果一个属性没有复杂规则,公开使用也可以。
例如:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
对于简单坐标点,直接公开 x 和 y 通常很自然。
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
表示 0 和 100 都合法。
商品数量:
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 要区分
方法中经常会用到 print 和 return。
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:封装学生成绩
要求:
- 定义
Student类。 - 有公开属性
name。 - 有私有属性
__score。 - 提供
set_score()修改成绩。 - 提供
get_score()查看成绩。 - 成绩必须在
0到100之间。
参考答案:
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:封装银行账户
要求:
- 定义
BankAccount类。 - 有公开属性
owner。 - 有私有属性
__balance。 - 提供
deposit()方法存款。 - 提供
withdraw()方法取款。 - 提供
get_balance()方法查看余额。 - 取款金额不能超过余额。
参考答案:
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 封装年龄
要求:
- 定义
Person类。 - 有私有属性
__age。 - 使用
property让外部通过person.age读取年龄。 - 使用 setter 限制年龄必须在
0到150之间。
参考答案:
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:只读面积属性
要求:
- 定义
Circle类。 - 有
radius属性。 - 使用
@property定义只读属性area。 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:封装商品库存
要求:
- 定义
Product类。 - 有公开属性
name。 - 有私有属性
__stock。 - 提供
add_stock()增加库存。 - 提供
sell()销售商品。 - 提供
get_stock()查看库存。 - 库存不能小于
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 类:
- 私有属性
__celsius表示摄氏温度。 - 使用
property读取和修改摄氏温度。 - 摄氏温度不能低于
-273.15。 - 提供只读属性
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 面向对象编程中的重要思想。
一句话总结:
封装就是把数据和操作数据的方法放在类中,
并控制外部如何访问和修改这些数据。
核心知识:
- 公开属性可以直接访问。
_属性名表示受保护属性,约定内部使用。__属性名表示私有属性,外部不能直接访问。- 私有属性通常通过公开方法访问。
- getter 用来读取数据。
- setter 用来修改数据。
- setter 中常常进行数据校验。
property可以让方法像属性一样使用。- 私有方法适合类内部辅助逻辑。
- 封装不是越多越好,要根据数据是否需要保护来决定。
常用代码模板:
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 更像属性,
校验数据更安全。
最后记住:
封装不是为了把代码藏起来,
而是为了让数据更安全,让使用更简单,让代码更清楚。