写在前面
欢迎回到我们的Python面向对象编程之旅!上一站,我们掌握了继承的奥秘,学会了如何让类之间建立家族关系,高效复用代码。相信你已经成功搭建了属于自己的图形家族树。
现在,是时候为你的代码世界增添两大核心利器:封装与多态。它们将让你的程序从"能用"变得"健壮"和"灵活"。本文将首先为你揭晓上期作业的参考答案,巩固继承思维。随后,我们将携手深入封装与多态的殿堂,学习如何保护代码隐私、赋予接口无限可能。
准备好了吗?让我们即刻出发!
任务回顾:你需要创建一个图形(Shape)基类,并派生出矩形(Rectangle)和圆形(Circle)类。进一步地,让正方形(Square)作为矩形的特例继承而来。核心目标是让所有图形都具备计算面积和周长的能力。
以下是一份清晰、可直接运行的参考答案,让我们一起来验证思路:
import math
classShape:
"""所有图形的蓝图(基类)"""
defarea(self):
"""计算面积 - 子类必须完成的具体任务"""
raise NotImplementedError("子类必须实现此方法")
defperimeter(self):
"""计算周长 - 子类必须完成的具体任务"""
raise NotImplementedError("子类必须实现此方法")
def__str__(self):
returnf"{self.__class__.__name__}"
classRectangle(Shape):
"""矩形类"""
def__init__(self, width, height):
self.width = width
self.height = height
defarea(self):
return self.width * self.height
defperimeter(self):
return2 * (self.width + self.height)
def__str__(self):
returnf"{super().__str__()}(width={self.width}, height={self.height})"
classCircle(Shape):
"""圆形类"""
def__init__(self, radius):
self.radius = radius
defarea(self):
return math.pi * self.radius ** 2
defperimeter(self):
return2 * math.pi * self.radius
def__str__(self):
returnf"{super().__str__()}(radius={self.radius})"
classSquare(Rectangle):
"""正方形类,继承自矩形(正方形是一种特殊的矩形)"""
def__init__(self, side):
# 巧妙调用父类初始化,将长和宽都设为边长
super().__init__(side, side)
self.side = side
def__str__(self):
# 为正方形提供更贴切的描述
returnf"{self.__class__.__name__}(side={self.side})"
# 让我们测试一下这个继承体系是否稳固
if __name__ == "__main__":
shapes = [
Rectangle(5, 3),
Circle(4),
Square(6)
]
for shape in shapes:
print(f"{shape}: 面积 = {shape.area():.2f}, 周长 = {shape.perimeter():.2f}")
运行结果:
Rectangle(width=5, height=3): 面积 = 15.00, 周长 = 16.00
Circle(radius=4): 面积 = 50.27, 周长 = 25.13
Square(side=6): 面积 = 36.00, 周长 = 24.00
设计亮点解析:
接口定义:Shape基类扮演了"指挥官"的角色,它声明了area和perimeter这两个必须完成的任务(使用NotImplementedError提示),为后续引入抽象类埋下伏笔。
逻辑复用:Square类通过super().__init__(side, side)一句代码,就完美继承了矩形的所有特性,生动体现了"正方形是长宽相等的矩形"这一关系。
多态初现:每个子类都定制了自己的__str__方法,用不同的方式展示自己。将它们放入同一个列表并用循环处理,这正是多态思想的雏形。
继承为我们勾勒了类的家族谱系。现在,我们要学习如何让每个家族成员(类)自身变得更强大、更聪明。
想象一下,你的手机是一个对象。你通过触摸屏(公共接口)来使用它,但无需关心内部复杂的芯片和电路(内部实现)。封装正是如此:
这样做的好处显而易见:
__(双下划线)约定Python崇尚"我们都是成年人"的信任文化,没有绝对的隐私。但它提供了一项绅士协议:名称改写(Name Mangling)。在属性或方法名前加上双下划线__,Python会在内部悄悄将其名字变形为_类名__属性名,从而实现一定程度的访问隔离。
classBankAccount:
"""银行账户类,演示封装如何守护你的'数字财富'"""
def__init__(self, owner, initial_balance=0):
self.owner = owner # 公开属性,账户持有人姓名
self.__balance = initial_balance # "私有"属性,账户余额,受保护
defdeposit(self, amount):
"""存款:只允许存入正数"""
if amount > 0:
self.__balance += amount
print(f"✅ 成功存入 {amount}元。当前余额:{self.__balance}元")
else:
print("❌ 存款失败:金额必须为正数!")
defwithdraw(self, amount):
"""取款:必须满足余额充足且金额有效"""
if0 < amount <= self.__balance:
self.__balance -= amount
print(f"✅ 成功取出 {amount}元。当前余额:{self.__balance}元")
return amount
else:
print("❌ 取款失败:金额无效或余额不足。")
return0
defget_balance(self):
"""查询余额:提供安全、只读的访问通道"""
return self.__balance
# 体验封装带来的安全感
account = BankAccount("小明", 1000)
print(f"账户所有者:{account.owner}") # 可以轻松访问
# print(account.__balance) # 直接访问被拒绝!AttributeError
print(f"通过安全接口查询余额:{account.get_balance()}元") # 正确方式
account.deposit(500) # 正常存款
account.withdraw(200) # 正常取款
account.withdraw(2000) # 触发安全规则,取款被拒
# 重要提示:Python的私有是"防君子不防小人"
# 通过变形后的名字理论上仍可访问,但这严重违背设计初衷,是危险操作。
# print(account._BankAccount__balance) # 不推荐!破坏封装性。
核心要义:
__setter和getter)来管理私有属性,就像银行为你提供ATM机和柜台服务一样。"多态"听起来高深,实则理念朴素:同一个命令,发给不同的对象,会产生各自独特的行为。就像对"绘画"这个指令,画家会作画,程序员会"画"流程图,机器人则可能执行绘图代码。
在Python的世界里,多态与生俱来(得益于"鸭子类型")。而在面向对象中,我们常通过继承和方法重写来优雅地实现它。
多态的魅力在于:
方法重写是子类展现个性的关键方式:子类对从父类继承来的方法进行重新定义。当调用该方法时,程序会优先执行子类自己的版本。
# 让我们重用之前创建的图形家族,感受多态的魔力
defprint_shape_info(shape_obj):
"""这是一个'万能'的函数,能友好地处理任何Shape家族的成员"""
# 此函数不关心传来的是矩形、圆形还是正方形,它只确信一点:你一定能计算面积和周长!
print(f"正在处理 {shape_obj} -> 面积: {shape_obj.area():.2f}, 周长: {shape_obj.perimeter():.2f}")
# 召集图形家族的几位成员
rect = Rectangle(10, 5)
circ = Circle(7)
sq = Square(4)
# 看!同一个函数,完美应对三种不同的图形
print_shape_info(rect)
print_shape_info(circ)
print_shape_info(sq)
# 更酷的是,我们可以把它们放在一个团队里统一管理
shape_team = [rect, circ, sq]
print("\n--- 图形团队批量报告 ---")
for member in shape_team:
print_shape_info(member)
运行展示:
正在处理 Rectangle(width=10, height=5) -> 面积: 50.00, 周长: 30.00
正在处理 Circle(radius=7) -> 面积: 153.94, 周长: 43.98
正在处理 Square(side=4) -> 面积: 16.00, 周长: 16.00
--- 图形团队批量报告 ---
正在处理 Rectangle(width=10, height=5) -> 面积: 50.00, 周长: 30.00
正在处理 Circle(radius=7) -> 面积: 153.94, 周长: 43.98
正在处理 Square(side=4) -> 面积: 16.00, 周长: 16.00
print_shape_info函数是多态的典范:它基于Shape制定的统一契约工作,却能智慧地应对所有遵守该契约的子类对象。未来若新增Triangle(三角形)类,此函数无需任何调整即可接纳新成员。
abc模块)在作业解答中,Shape基类通过抛出异常来提示子类实现方法。Python提供了更优雅、更专业的工具——abc模块来定义这种接口契约。
抽象类本身不能被实例化,它的使命就是为子孙后代划定必须遵守的"家规"(抽象方法)。
from abc import ABC, abstractmethod
import math
classShape(ABC): # 继承ABC,宣告这是一个抽象基类
"""图形的'宪法':所有子类必须遵守的规范"""
@abstractmethod # 此装饰器标记的方法是'宪法条款',必须被实现
defarea(self):
"""计算面积 - 具体算法由子类决定"""
pass
@abstractmethod
defperimeter(self):
"""计算周长 - 具体算法由子类决定"""
pass
def__str__(self):
"""非抽象方法,子类可直接继承或重写"""
returnf"{self.__class__.__name__}"
# 试图实例化'宪法'本身是徒劳的
# s = Shape() # TypeError: 无法实例化抽象类Shape...
classConcreteCircle(Shape):
"""一个遵守'宪法'的具体公民类"""
def__init__(self, radius):
self.radius = radius
defarea(self): # 履行'宪法'义务:实现area方法
return math.pi * self.radius ** 2
defperimeter(self): # 履行'宪法'义务:实现perimeter方法
return2 * math.pi * self.radius
def__str__(self):
returnf"{super().__str__()}(radius={self.radius})"
# 只有完全履行了义务,才能被成功创建
my_circle = ConcreteCircle(5)
print(f"{my_circle}: 面积 = {my_circle.area():.2f}")
抽象类的三大使命:
理论知识已就位,现在让我们迎接一个综合挑战,将继承、封装、多态融会贯通!
你的任务是设计一个小型系统,管理两种员工:Developer(开发者)和Manager(经理)。他们既有共性,也有特性。你需要运用封装来保护核心数据,并利用多态来统一处理他们的薪资计算。
Employee,定义所有员工的共同属性和必须实现的接口。Developer和Manager两个子类,让它们以不同的方式计算薪资(体现多态)。请根据以下测试场景来验证你的实现。当你的代码能产生如下预期输出时,恭喜你,成功通关!
# 通关测试代码
if __name__ == "__main__":
# 1. 创建两位员工
dev1 = Developer("Alice", "D001", 5000, "Python")
manager1 = Manager("Bob", "M001", 8000, 3)
# 2. 测试信息获取(应通过安全接口)
print(dev1.get_details())
print(manager1.get_details())
# 3. 测试多态威力:统一计算薪资
employees = [dev1, manager1]
print("\n📊 月度薪资报告:")
for emp in employees:
print(f" - {emp.name}: ¥{emp.calculate_salary()}")
# 4. 测试封装有效性(以下两行请注释掉,它们应无法正常工作)
# dev1.__id = "new_id" # 这不会修改到真正的私有属性!
# print(dev1.__id) # 这里会报错,访问不到。
# 5. 测试子类的特有功能
dev1.add_skill("Java")
print(f"\n{dev1.name}的技能列表:{dev1.display_skills()}")
manager1.add_subordinate("Charlie")
print(f"{manager1.name}的团队下属:{manager1.display_subordinates()}")
预期输出:
员工姓名:Alice, 职位:Developer, 技能:Python
员工姓名:Bob, 职位:Manager, 下属人数:3
📊 月度薪资报告:
- Alice: ¥5000
- Bob: ¥9200
Alice的技能列表:Python, Java
Bob的团队下属:Charlie
恭喜你!你已经成功掌握了面向对象编程的三大核心支柱:继承、封装和多态。让我们一起回顾一下它们的重要性和应用场景:
super()函数调用父类方法,方法重写实现个性化__abc模块)self.__private_attr = value | ||
def __private_method(self): | ||
_ClassName__private_attr | ||
def get_attr(self): return self.__private_attr | ||
@property |
示例代码:
classPerson:
def__init__(self, name, age):
self.name = name # 公共属性
self.__age = age # 私有属性
defget_age(self): # getter方法
return self.__age
defset_age(self, new_age): # setter方法,带验证
if new_age > 0:
self.__age = new_age
else:
raise ValueError("年龄必须为正数")
# 使用@property装饰器的更优雅方式
@property
defage(self):
return self.__age
@age.setter
defage(self, new_age):
if new_age > 0:
self.__age = new_age
else:
raise ValueError("年龄必须为正数")
parent = Child() | ||
for obj in objects: obj.common_method() |
示例代码:
# 鸭子类型示例
defmake_sound(animal):
animal.speak() # 只要有speak方法,就可以调用
classDog:
defspeak(self):
print("汪汪汪")
classCat:
defspeak(self):
print("喵喵喵")
# 不同类型的对象,统一接口调用
dog = Dog()
cat = Cat()
make_sound(dog) # 输出:汪汪汪
make_sound(cat) # 输出:喵喵喵
class AbstractClass(ABC): | ||
@abstractmethod | ||
obj = AbstractClass() |
示例代码:
from abc import ABC, abstractmethod
classShape(ABC):
@abstractmethod
defarea(self):
pass
@abstractmethod
defperimeter(self):
pass
classCircle(Shape):
def__init__(self, radius):
self.radius = radius
defarea(self):
return3.14 * self.radius ** 2
defperimeter(self):
return2 * 3.14 * self.radius
# 可以实例化具体子类
circle = Circle(5)
print(circle.area()) # 输出:78.5
print(circle.perimeter()) # 输出:31.4
# 无法实例化抽象类
# shape = Shape() # TypeError: Can't instantiate abstract class Shape...
面向对象编程是一种强大的编程范式,它通过封装、继承和多态三大支柱,帮助我们构建更加模块化、可维护和可扩展的代码。掌握这些核心概念和技术,将为你的Python编程之旅打下坚实的基础。
在实际开发中,你会发现面向对象设计的魅力:它不仅能让你的代码更加优雅,还能帮助你更好地理解和建模现实世界的问题。
请在微信客户端打开