封装是面向对象编程中的一个重要思想。
通俗地说:
举一个生活中的例子:
在程序中也是一样。
一个对象内部可能有很多数据,但外部代码不应该随便乱改。
我们希望:
这就是封装。
先看一个没有封装的例子。
def__init__(self, name, score):输出:
问题出现了:
因为成绩通常应该在 0 到 100 之间。
如果外部代码可以随便修改对象属性,就容易产生不合法的数据。
封装可以解决这个问题。
我们可以让外部不要直接修改成绩,而是通过方法修改。
def__init__(self, name, score):defupdate_score(self, new_score):print("成绩必须在 0 到 100 之间")输出:
这样就避免了不合理的数据进入对象。
封装主要解决三个问题。
不让外部随便修改重要数据。
例如:
把数据和操作数据的方法放在一个类中。
例如:
BankAccount 类中放余额数据,也放存款和取款方法。Student 类中放成绩数据,也放修改成绩和判断是否及格的方法。使用者不需要知道对象内部怎么实现,只需要知道可以调用哪些方法。
例如:
外部只需要知道:
不用关心余额在内部怎么保存。
封装可以用一句话总结:
也可以这样理解:
封装不是为了把所有东西都藏起来。
封装是为了让数据访问更合理、更安全、更清晰。
在一些编程语言中,属性和方法有严格的访问权限,例如:
Python 的访问控制没有那么严格。
Python 更强调约定。
常见命名方式:
示例:
这里:
公开属性就是普通属性。
外部可以直接访问和修改。
输出:
公开属性适合保存那些允许外部直接访问和修改的数据。
例如:
但是如果一个属性需要限制修改规则,就不适合完全公开。
受保护属性通常以一个下划线 _ 开头。
例如:
def__init__(self, name, score):这里的 _score 表示:
注意:
也就是说,外部仍然可以访问。
def__init__(self, name, score):输出:
虽然可以访问,但不推荐。
私有属性以两个下划线 __ 开头。
示例:
def__init__(self, name, score):运行后:
因为 __score 是私有属性,外部不能直接用 对象.__score 访问。
私有属性适合保存不希望外部直接修改的数据。
例如:
私有属性的作用不是为了绝对安全,而是为了限制外部随意访问。
例如:
def__init__(self, owner, balance):account = BankAccount("张三", 1000)owner 可以访问。
__balance 不能直接访问。
这样做的目的是:
如果余额可以被随便改:
account.balance = -999999就会产生严重问题。
Python 中的私有属性不是真正意义上的绝对隐藏。
Python 会把私有属性名做名称改写。
例如:
__score 在内部大致会被改写成:
所以 technically 可以这样访问:
print(stu._Student__score)但是非常不推荐这样写。
如果外部需要读取私有属性,应该提供公开方法。
例如:
def__init__(self, name, score):输出:
这里:
如果外部需要修改私有属性,也应该提供方法。
示例:
def__init__(self, name, score):defset_score(self, score):print("成绩必须在 0 到 100 之间")输出:
这样做的好处是:
在面向对象中,读取属性的方法通常叫 getter。
修改属性的方法通常叫 setter。
例如:
这是 getter。
defset_score(self, score):这是 setter。
完整示例:
def__init__(self, score):defset_score(self, score):输出:
getter 和 setter 很清楚,但调用起来稍微有点麻烦。
Python 提供了 property,可以让方法像属性一样使用。
示例:
def__init__(self, score):print("成绩必须在 0 到 100 之间")输出:
这里看起来像直接访问属性:
其实背后调用的是方法。
property 的好处是:
例如:
看起来像直接赋值。
但实际上会调用:
这就实现了:
如果只写 @property,不写 setter,那么这个属性就是只读的。
示例:
def__init__(self, radius):return3.14 * self.radius * self.radius输出:
如果尝试修改:
会报错。
因为 area 是根据 radius 计算出来的,不应该直接修改。
这种场景很适合使用只读属性。
有些属性并不是直接保存的,而是根据其他属性计算出来的。
例如矩形面积:
def__init__(self, width, height):returnself.width * self.height输出:
这里 area 没有直接保存。
它是通过:
计算出来的。
这种方式可以保证:
只要 width 或 height 变化,area 也会自动得到新的计算结果。除了私有属性,也可以定义私有方法。
私有方法以两个下划线 __ 开头。
示例:
def__init__(self, name, score):def__check_score(self, score):defset_score(self, score):ifself.__check_score(score):输出:
这里:
如果外部这样调用:
会报错。
私有方法通常用于类内部的小步骤。
例如:
示例:
def__init__(self, username, password):self.__password = passworddef__check_password_length(self, password):defchange_password(self, new_password):ifself.__check_password_length(new_password):self.__password = new_passworduser = User("alice", "123456")user.change_password("abc")user.change_password("abcdef")输出:
封装后,外部代码通过类提供的公开方法来使用对象。
这些公开方法可以理解为对象的“接口”。
例如:
def__init__(self, owner, balance):defwithdraw(self, money):elif money > self.__balance:外部使用:
account = BankAccount("张三", 1000)print(account.get_balance())外部只需要知道:
不需要知道余额在内部如何保存。
这就是封装带来的好处。
下面是一个比较完整的封装案例。
def__init__(self, owner, balance=0):defwithdraw(self, money):if money > self.__balance:account = BankAccount("张三", 1000)print(account.get_balance())输出:
这个类中:
为什么 __balance 要私有?
def__init__(self, name, score):defset_score(self, score):print("成绩必须在 0 到 100 之间")输出:
这里有一个细节:
先给 __score 一个默认值,然后通过 set_score() 设置真实成绩。
这样创建对象时也会进行成绩校验。
def__init__(self, name, score):print("成绩必须在 0 到 100 之间")输出:
这个版本中:
商品库存不能小于 0。
可以使用封装来控制。
def__init__(self, name, price, stock):defadd_stock(self, count):elif count > self.__stock:product = Product("苹果", 5, 100)print(product.get_stock())输出:
这里:
密码不应该随便被外部读取。
可以这样设计:
def__init__(self, username, password):self.__password = passworddefcheck_password(self, password):returnself.__password == passworddefchange_password(self, old_password, new_password):ifnotself.check_password(old_password):self.__password = new_passworduser = 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")输出:
这个案例中:
不是所有属性都必须写成私有属性。
如果属性很普通,外部直接访问没有风险,可以使用公开属性。
例如:
def__init__(self, title, author):这里 title 和 author 公开通常没问题。
如果属性需要校验,或者不希望外部直接修改,就可以封装。
例如:
这些数据通常需要规则限制。
初学者容易产生一种误解:
这不是必须的。
封装的目的不是让代码变复杂。
封装的目的是:
如果一个属性没有复杂规则,公开使用也可以。
例如:
对于简单坐标点,直接公开 x 和 y 通常很自然。
常见命名建议:
| | |
|---|
| name | |
| _score | |
| __balance | |
| deposit() | |
| __check_money() | |
示例:
def__init__(self, owner, balance):def__check_money(self, money):ifself.__check_money(money):这里:
含义:
示例:
可以输出:
含义:
示例:
会报错。
封装最常见的用途之一,就是数据校验。
例如年龄不能为负数。
def__init__(self, name, age):print("年龄必须在 0 到 150 之间")person = Person("张三", 18)输出:
如果没有封装:
就可能让对象进入不合理状态。
封装不仅是为了保护数据,也可以隐藏实现细节。
例如,订单金额的计算可能比较复杂。
外部只需要调用:
不需要知道内部如何计算。
示例:
def__init__(self, price, count, discount): original = self.price * self.countreturn original * self.discountorder = Order(100, 3, 0.8)print(order.total_price())输出:
外部只关心结果,不关心计算细节。
这也是封装。
封装还有一个好处:
例如,原来余额直接保存在 __balance 中:
后来想增加手续费、冻结金额等内部逻辑。
只要 get_balance() 的使用方式不变,外部代码就不需要大改。
这就是封装的价值:
错误理解:
__password 写成私有后,别人绝对访问不到。实际情况:
Python 的私有属性只是名称改写,不是绝对安全机制。不要把敏感信息的安全完全依赖于双下划线。
错误写法:
会报错。
正确做法:
不推荐:
defset_score(self, score):这样写虽然用了私有属性,但没有起到保护作用。
更推荐:
defset_score(self, score):封装的重点不只是“藏起来”,还要“按规则访问”。
错误写法:
def__init__(self, score):defset_score(self, score):这样创建对象时:
仍然可以得到不合法成绩。
更推荐:
def__init__(self, score):defset_score(self, score):这样创建对象时也会校验数据。
37. 常见错误 5:property 名称和私有属性名混乱推荐写法:
def__init__(self, score):不推荐把公开属性和私有属性混在一起乱命名。
例如:
同时又写:
这种写法可能导致递归错误。
错误示例:
这里 return self.score 会再次调用 score 属性,容易无限递归。
正确写法:
def__init__(self, score):不推荐:
如果外部需要调用这个方法,就不应该写成私有方法。
私有方法适合类内部使用。
公开方法适合外部调用。
判断方法:
有些初学者会把每个属性都写成:
如果这个属性没有任何校验规则,也没有隐藏必要,就会让代码变得啰嗦。
例如:
def__init__(self, title):defset_title(self, title):如果 title 只是普通书名,直接公开也可以:
def__init__(self, title):40. 常见错误 8:通过名称改写强行访问私有属性虽然可以这样访问:
但是不推荐。
原因:
正确做法:
或者使用:
前提是类中定义了 property。
不是所有数据都需要私有化。
需要保护的数据通常有:
普通数据可以公开。
例如:
是否私有,取决于这个数据是否需要控制访问。
公开方法是给外部使用的,所以名字应该清楚。
推荐:
不推荐:
私有方法通常不应该承担外部业务入口。
例如:
def__check_score(self, score):这是合理的内部辅助方法。
外部真正调用的是:
defset_score(self, score):封装不是孤立的。
一个类应该有清楚的职责。
例如:
BankAccount 管理账户信息和存取款行为。不要把很多不相关的功能都塞进一个类。
例如:
Student 类里同时处理银行余额、商品库存、文件下载。这样会让封装失去意义。
45. 注意事项 5:封装后外部应该少依赖内部细节如果一个属性是私有的,外部就不应该依赖它的具体名字。
例如,不应该写:
account._BankAccount__balance应该写:
这样即使类内部以后改成:
只要 get_balance() 还在,外部代码就不需要改。
例如成绩:
通常:
表示 0 和 100 都合法。
商品数量:
表示必须大于 0。
47. 注意事项 7:setter 不一定必须返回值很多 setter 方法只负责修改属性,不需要返回值。
例如:
defset_score(self, score):如果需要告诉外部修改是否成功,也可以返回布尔值。
defset_score(self, score):48. 注意事项 8:print 和 return 要区分方法中经常会用到 print 和 return。
return 是把结果交给调用者。
print 是直接打印结果。
如果方法名是 get_score(),通常应该使用 return。
如果方法名是 show_score(),通常可以使用 print。
def__init__(self, name, age, score):print("年龄必须在 0 到 150 之间")defset_score(self, score):print("成绩必须在 0 到 100 之间")print(f"姓名:{self.name},年龄:{self.__age},成绩:{self.__score}")stu = Student("张三", 18, 90)输出:
这个案例中:
set_age() 和 set_score() 负责修改并校验数据。get_age() 和 get_score() 负责读取数据。def__init__(self, width, height):returnself.__width * self.__heightreturn (self.__width + self.__height) * 2输出:
这个案例适合讲:
area 和 perimeter 是只读计算属性。要求:
参考答案:
def__init__(self, name, score):defset_score(self, score):要求:
参考答案:
def__init__(self, owner, balance=0):defwithdraw(self, money):elif money > self.__balance:account = BankAccount("张三", 1000)print(account.get_balance())53. 练习 3:使用 property 封装年龄要求:
使用 property 让外部通过 person.age 读取年龄。使用 setter 限制年龄必须在 0 到 150 之间。参考答案:
要求:
使用 @property 定义只读属性 area。参考答案:
def__init__(self, radius):return3.14 * self.radius * self.radius要求:
参考答案:
def__init__(self, name, stock):defadd_stock(self, count):elif count > self.__stock:product = Product("苹果", 100)print(product.get_stock())下面代码有什么问题?
参考答案:
__score 是私有属性,不能在类外通过 stu.__score 直接访问。应该提供 get_score() 方法,或使用 property。下面哪些属性更适合封装?
参考答案:
原因:
补全代码,让价格不能小于 0。
题目:
def__init__(self, price):defset_price(self, price):参考答案:
def__init__(self, price):defset_price(self, price):59. 练习 9:把 getter/setter 改成 property原代码:
def__init__(self, score):defset_score(self, score):请改成 property 写法。
参考答案:
def__init__(self, score):要求设计一个 Temperature 类:
提供只读属性 fahrenheit,返回华氏温度。参考答案:
def__init__(self, celsius):returnself.__celsius * 9 / 5 + 32封装是 Python 面向对象编程中的重要思想。
一句话总结:
核心知识:
常用代码模板:
def__init__(self, value):defset_value(self, value):property 模板:
def__init__(self, value):defvalue(self, new_value):记忆口诀:
最后记住:
而是为了让数据更安全,让使用更简单,让代码更清楚。