Day 15:【99天精通Python】面向对象编程(OOP)中篇 - 封装、继承与多态
前言
欢迎来到第15天!
在昨天的课程中,我们学会了如何定义类和创建对象。但这只是 OOP 的冰山一角。面向对象编程之所以强大,归功于它的三大核心特性:封装 (Encapsulation)、继承 (Inheritance)和多态 (Polymorphism)。
这三个词听起来很高大上,但实际上它们是为了解决我们在编程中遇到的实际问题:
- 封装:为了保护数据,不让别人随意修改。
- 继承:为了偷懒(复用代码),爸爸有的儿子不用再写一遍。
- 多态:为了灵活,同一个指令让不同的对象做不同的事。
本节内容:
- 私有属性与封装
- 继承的基本语法
- 方法重写 (Overriding)
super()函数- 多态与鸭子类型
- 实战练习
一、封装 (Encapsulation):保护你的数据
1.1 公有 vs 私有
默认情况下,Python 类中的属性都是公有 (Public)的,在类的外部可以随意访问和修改。这有时候很不安全。
classAccount:def__init__(self,balance):self.balance=balance acc=Account(100)acc.balance=-999999# 外部可以直接修改,这太危险了!为了保护数据,我们可以将属性定义为私有 (Private)。在 Python 中,只需要在属性名前加两个下划线__。
1.2 私有属性的使用
classAccount:def__init__(self,balance):self.__balance=balance# 私有属性defget_balance(self):"""提供公开的方法获取余额"""returnself.__balancedefdeposit(self,amount):"""提供公开的方法修改余额,可以在这里加逻辑判断"""ifamount>0:self.__balance+=amountelse:print("金额无效")acc=Account(1000)# print(acc.__balance) # 报错!外部无法访问print(acc.get_balance())# 1000 (通过方法访问)acc.deposit(500)原理:Python 实际上是将
__balance改名为了_Account__balance(名称改写),虽然硬要访问也能访问,但君子协定我们要遵守规则。
二、继承 (Inheritance):子承父业
2.1 为什么要继承?
假设我们要写Cat和Dog两个类,它们都有name属性,都会eat。如果分别写两个类,代码就重复了。
我们可以提取一个父类Animal,让Cat和Dog去继承它。
2.2 定义继承
# 父类 (基类)classAnimal:def__init__(self,name):self.name=namedefeat(self):print(f"{self.name}正在吃饭...")defsleep(self):print(f"{self.name}正在睡觉...")# 子类 (派生类) 继承 AnimalclassDog(Animal):defbark(self):print("汪汪汪!")classCat(Animal):defmeow(self):print("喵喵喵!")# 测试dog=Dog("旺财")dog.eat()# 继承自父类的方法 -> 旺财 正在吃饭...dog.bark()# 子类独有的方法 -> 汪汪汪!2.3 方法重写 (Overriding)
如果子类对父类的方法不满意,可以重新定义它。
classBird(Animal):defeat(self):print(f"{self.name}正在啄米吃...")# 重写父类的 eatbird=Bird("波利")bird.eat()# 波利 正在啄米吃...2.4 super() 函数
在子类中,如果想调用父类的方法(特别是在__init__中),需要用到super()。
classDog(Animal):def__init__(self,name,breed):# 调用父类的 __init__ 初始化 namesuper().__init__(name)# 再初始化子类独有的 breedself.breed=breed d=Dog("来福","哈士奇")print(f"{d.name}是{d.breed}")三、多态 (Polymorphism):一种接口,多种形态
3.1 什么是多态?
多态是指:不同的子类对象调用相同的父类方法,产生不同的执行结果。
classAnimal:defspeak(self):passclassDog(Animal):defspeak(self):print("汪汪!")classCat(Animal):defspeak(self):print("喵喵!")defmake_noise(animal_obj):"""这个函数接收任何 Animal 对象"""animal_obj.speak()d=Dog("A")c=Cat("B")make_noise(d)# 汪汪!make_noise(c)# 喵喵!3.2 鸭子类型 (Duck Typing)
Python 是一种动态语言,它并不严格要求继承体系。
“如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子。”
哪怕一个类没有继承Animal,只要它也有speak()方法,上面的make_noise函数照样能处理它。
classCar:defspeak(self):print("滴滴!")car=Car()make_noise(car)# 滴滴! (Python 不检查类型,只检查有没有这个方法)四、实战练习
练习1:工资管理系统 (继承与多态)
- 定义父类
Employee,属性name,方法get_pay()。 - 定义子类
FullTimeEmployee,属性salary(月薪),重写get_pay()返回月薪。 - 定义子类
PartTimeEmployee,属性hours,rate(时薪),重写get_pay()返回hours * rate。 - 编写一个函数计算公司所有员工的总工资。
classEmployee:def__init__(self,name):self.name=namedefget_pay(self):return0classFullTimeEmployee(Employee):def__init__(self,name,salary):super().__init__(name)self.salary=salarydefget_pay(self):returnself.salaryclassPartTimeEmployee(Employee):def__init__(self,name,hours,rate):super().__init__(name)self.hours=hours self.rate=ratedefget_pay(self):returnself.hours*self.rate# 统计总支出staffs=[FullTimeEmployee("Alice",6000),FullTimeEmployee("Bob",8000),PartTimeEmployee("Charlie",50,20)# 50小时 * 20元 = 1000]total_pay=sum(emp.get_pay()forempinstaffs)print(f"公司本月总工资支出:{total_pay}")# 15000五、OOP 类图结构
我们可以用 Mermaid 来直观地展示继承关系。
六、常见问题
Q1:私有方法怎么定义?
和属性一样,方法名前加双下划线。例如def __secret_method(self):,只能在类内部被self.__secret_method()调用。
Q2:Python 支持多继承吗?
支持。class C(A, B):表示 C 同时继承 A 和 B。但这会带来复杂性(如菱形继承问题),初学者建议先掌握单继承。
Q3:isinstance是什么?
用来判断对象是否属于某个类。
d=Dog("A")print(isinstance(d,Dog))# Trueprint(isinstance(d,Animal))# True (因为Dog继承Animal)print(isinstance(d,Cat))# False七、小结
关键要点:
- 封装:用
__隐藏内部细节,提供安全接口。 - 继承:子类自动拥有父类的功能,
super()是连接父子的桥梁。 - 多态:让代码更灵活,关注点从"是什么类型"转变为"能做什么"。
八、课后作业
- 图形计算器:
- 定义父类
Shape,有一个方法area()返回 0。 - 定义子类
Circle(属性半径) 和Square(属性边长)。 - 分别重写
area()方法。 - 创建一个列表包含不同的图形,遍历打印它们的面积。
- 定义父类
- 游戏角色:
- 定义
Hero类,包含hp(血量) 和attack()方法。 - 定义
Mage(法师) 和Warrior(战士) 继承Hero。 - 法师攻击时打印"释放火球",战士攻击时打印"挥舞大剑"。
- 尝试给法师增加一个
mp(魔法值) 属性,并在攻击时消耗魔法。
- 定义
下节预告
Day 16:面向对象编程(OOP)下篇 - 魔术方法与类属性-__str__是什么?__add__是什么?静态方法又是什么?明天我们将探索类的更多高级玩法!
系列导航:
- 上一篇:Day 14 - 面向对象编程(上)
- 下一篇:Day 16 - 面向对象编程(下)(待更新)