第55集:封装特性
学习目标
- 理解封装的概念和作用
- 掌握Python中的访问修饰符(public、protected、private)
- 学会使用属性装饰器(@property)
- 理解getter和setter方法
- 掌握数据验证和访问控制
- 学会设计良好的接口
一、封装概述
1.1 什么是封装
封装:将对象的属性和方法包装在一起,隐藏内部实现细节,只暴露必要的接口。
1.2 封装的作用
┌─────────────────────────────────────┐
│ 外部世界 │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 公共接口 (Public) │ ← 只暴露必要的
├─────────────────────────────────────┤
│ 受保护的接口 (Protected) │
├─────────────────────────────────────┤
│ 私有实现 (Private) │ ← 隐藏内部细节
└─────────────────────────────────────┘封装的好处:
- 保护数据安全
- 隐藏实现细节
- 降低耦合度
- 提高可维护性
- 便于修改和扩展
1.3 封装示例
class BankAccount:
def __init__(self, account_number, initial_balance):
self.account_number = account_number
self.__balance = initial_balance # 私有属性
def deposit(self, amount):
"""存款 - 公共接口"""
if amount > 0:
self.__balance += amount
return True
return False
def withdraw(self, amount):
"""取款 - 公共接口"""
if 0 < amount <= self.__balance:
self.__balance -= amount
return True
return False
def get_balance(self):
"""查询余额 - 公共接口"""
return self.__balance
# 使用
account = BankAccount("001", 1000)
account.deposit(500)
account.withdraw(200)
print(account.get_balance()) # 输出:1300
# 不能直接访问 account.__balance二、Python中的访问修饰符
2.1 公共属性(Public)
特征:
- 没有特殊前缀
- 可以在类外部直接访问
- 没有任何访问限制
示例:
class Person:
def __init__(self, name, age):
self.name = name # 公共属性
self.age = age # 公共属性
p = Person("张三", 18)
print(p.name) # 直接访问
p.age = 20 # 直接修改2.2 受保护的属性(Protected)
特征:
- 以单个下划线
_开头 - 约定俗成的私有,实际上可以访问
- 表示"不建议在类外部访问"
示例:
class Person:
def __init__(self, name, age):
self.name = name
self._age = age # 受保护的属性
p = Person("张三", 18)
print(p._age) # 可以访问,但不建议
p._age = 20 # 可以修改,但不建议2.3 私有属性(Private)
特征:
- 以双下划线
__开头 - Python会进行名称改写(name mangling)
- 外部不能直接访问
示例:
class Person:
def __init__(self, name, age):
self.name = name
self.__age = age # 私有属性
p = Person("张三", 18)
# print(p.__age) # 错误:AttributeError
# p.__age = 20 # 错误:创建了一个新属性名称改写机制:
class Person:
def __init__(self):
self.__private = "私有"
p = Person()
# Python将其改写为:_Person__private
print(p._Person__private) # 可以访问,但不推荐三、属性装饰器(@property)
3.1 基本用法
class Person:
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
"""getter方法"""
return self._name
@name.setter
def name(self, value):
"""setter方法"""
if isinstance(value, str):
self._name = value
else:
raise ValueError("姓名必须是字符串")
p = Person("张三", 18)
print(p.name) # 调用getter
p.name = "李四" # 调用setter3.2 只读属性
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@property
def area(self):
"""只读属性:计算面积"""
return 3.14159 * self._radius ** 2
c = Circle(5)
print(c.radius) # 输出:5
print(c.area) # 输出:78.53975
# c.area = 100 # 错误:只读属性不能赋值3.3 带验证的属性
class Student:
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score
@property
def score(self):
return self._score
@score.setter
def score(self, value):
"""验证分数"""
if not isinstance(value, (int, float)):
raise TypeError("分数必须是数字")
if value < 0 or value > 100:
raise ValueError("分数必须在0-100之间")
self._score = value
s = Student("张三", 18, 85)
print(s.score) # 输出:85
s.score = 90 # 正常修改
try:
s.score = 150 # 会报错
except ValueError as e:
print(e) # 输出:分数必须在0-100之间四、完整封装示例
4.1 银行账户类
class BankAccount:
def __init__(self, account_number, owner, initial_balance=0):
"""初始化账户"""
self.account_number = account_number # 公共属性
self.owner = owner # 公共属性
self.__balance = initial_balance # 私有属性
self._min_balance = 100 # 受保护的常量
self.__transactions = [] # 私有列表
@property
def balance(self):
"""获取余额"""
return self.__balance
def deposit(self, amount):
"""存款"""
if amount <= 0:
raise ValueError("存款金额必须大于0")
self.__balance += amount
self.__add_transaction("存款", amount)
return True
def withdraw(self, amount):
"""取款"""
if amount <= 0:
raise ValueError("取款金额必须大于0")
if self.__balance - amount < self._min_balance:
raise ValueError("余额不足")
self.__balance -= amount
self.__add_transaction("取款", -amount)
return True
def __add_transaction(self, type_, amount):
"""私有方法:添加交易记录"""
self.__transactions.append({
"type": type_,
"amount": amount,
"balance": self.__balance
})
def get_transactions(self):
"""获取交易记录"""
return self.__transactions.copy()
# 使用
account = BankAccount("6222000012345678", "张三", 1000)
account.deposit(500)
account.withdraw(200)
print(f"余额: {account.balance}")
print(f"交易记录: {account.get_transactions()}")4.2 温度转换类
class Temperature:
def __init__(self, celsius):
self.__celsius = celsius
@property
def celsius(self):
"""获取摄氏度"""
return self.__celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("温度不能低于绝对零度")
self.__celsius = value
@property
def fahrenheit(self):
"""获取华氏度"""
return self.__celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, value):
self.__celsius = (value - 32) * 5/9
@property
def kelvin(self):
"""获取开尔文"""
return self.__celsius + 273.15
temp = Temperature(25)
print(f"摄氏度: {temp.celsius}")
print(f"华氏度: {temp.fahrenheit}")
print(f"开尔文: {temp.kelvin}")
# 通过不同单位设置
temp.fahrenheit = 86
print(f"设置华氏度86后,摄氏度: {temp.celsius}")五、设计良好的接口
5.1 接口设计原则
- 最小化公共接口:只暴露必要的方法和属性
- 隐藏实现细节:使用私有属性封装内部逻辑
- 提供清晰的文档:说明每个方法和属性的用途
- 进行参数验证:确保数据的正确性
5.2 示例:设计良好的类
class EmailValidator:
"""邮箱验证器"""
def __init__(self):
self.__valid_domains = set() # 有效的域名
def add_valid_domain(self, domain):
"""添加有效域名"""
if isinstance(domain, str) and domain:
self.__valid_domains.add(domain)
def is_valid(self, email):
"""验证邮箱"""
if not isinstance(email, str):
return False
if "@" not in email:
return False
parts = email.split("@")
if len(parts) != 2:
return False
local, domain = parts
if not local or not domain:
return False
return domain in self.__valid_domains
validator = EmailValidator()
validator.add_valid_domain("gmail.com")
validator.add_valid_domain("163.com")
print(validator.is_valid("user@gmail.com")) # 输出:True
print(validator.is_valid("user@invalid.com")) # 输出:False六、常见错误与注意事项
6.1 常见错误
- 过度封装:
class BadExample:
def __init__(self):
self.__value = 10
def get_value(self):
return self.__value
def set_value(self, value):
self.__value = value
# 不必要的getter和setter
# 应该使用@property- 忘记使用名称改写:
class Person:
def __init__(self):
self.__age = 18
p = Person()
# print(p.__age) # 错误:AttributeError
# 应该使用:print(p._Person__age)6.2 注意事项
- Python的私有不是绝对的:通过名称改写仍然可以访问
- 私有主要用于提醒开发者:遵循约定可以提高代码质量
- 使用@property实现更好的封装:比getter/setter更优雅
- 适当使用保护级别:不是所有属性都需要私有
七、课后练习
练习题
编程题
- 定义一个
Rectangle类 - 使用私有属性存储长和宽
- 使用@property实现属性访问
- 添加面积和周长的只读属性
- 定义一个
编程题
- 定义一个
Student类 - 封装姓名、年龄、成绩等属性
- 添加数据验证
- 计算平均分和等级
- 定义一个
思考题
- 为什么要使用封装?
- 什么时候应该使用私有属性?
- @property和getter/setter有什么区别?
八、本集总结
核心知识点回顾
- 封装概念:隐藏实现细节,暴露公共接口
- 访问修饰符:public、protected(_)、private(__)
- @property:实现优雅的属性访问
- 数据验证:在setter中验证数据
- 接口设计:最小化公共接口
下集预告
下一集我们将学习继承基础,深入了解如何通过继承实现代码复用。
学习建议: 理解封装的思想,合理使用访问修饰符和@property。通过实际编程练习,掌握良好的封装设计。