第28课:继承与方法重写
学习目标
- 理解继承的概念:能够解释什么是类继承,以及它如何帮助实现代码复用和建立类层次结构。
- 掌握方法重写的语法与目的:学会在子类中重新定义父类的方法,并理解其在实际编程中的应用场景。
- 学会调用父类方法:能够使用
super()函数在子类中安全、正确地调用被重写的父类方法,实现功能的扩展而非完全替换。
核心概念
1. 什么是继承?
想象一下,你有一个"交通工具"的基类,它定义了所有交通工具都具有的属性(如颜色、速度)和方法(如启动、停止)。现在,你想创建"汽车"和"飞机"两个类。它们都是交通工具,但又有自己独特的特性(汽车有轮子,飞机有翅膀)。与其从头开始写"汽车"和"飞机"类,不如让它们继承"交通工具"类。
- 父类(Parent Class / Base Class):被继承的类(如
交通工具)。 - 子类(Child Class / Derived Class):继承而来的类(如
汽车)。 - 继承关系:子类自动拥有父类所有公开的属性和方法,并可以在此基础上进行扩展或修改。
继承最大的好处是代码复用和建立逻辑层次。我们不需要在每个子类中重复编写相同的代码。
2. 什么是方法重写?
继承之后,子类拥有了父类的方法。但是,子类可能觉得父类的某个方法行为不太符合自己的需求。例如,父类 动物 有一个 speak() 方法打印"...",但子类 狗 希望 speak() 方法打印"汪汪汪"。这时,我们就可以在子类中重新定义一个与父类同名的方法,这个过程就叫做方法重写(Override)。
重写后,当你通过子类对象调用该方法时,会优先执行子类重写后的版本,而不是父类的版本。
代码示例
示例1:基本的继承与方法重写
# 定义一个父类:动物 (Animal)
class Animal:
def __init__(self, name):
self.name = name # 所有动物都有名字
def speak(self):
"""父类的 speak 方法,提供一个通用实现"""
print(f"{self.name} 发出了一些声音。")
# 定义一个子类:狗 (Dog),继承自 Animal
class Dog(Animal):
def speak(self):
"""重写父类的 speak 方法,实现狗特有的行为"""
print(f"{self.name} 说:汪汪汪!")
# 定义另一个子类:猫 (Cat),继承自 Animal
class Cat(Animal):
def speak(self):
"""重写父类的 speak 方法"""
print(f"{self.name} 说:喵喵喵~")
# 测试继承和重写
if __name__ == "__main__":
# 创建一个通用的动物对象
generic_animal = Animal("小动物")
generic_animal.speak() # 调用父类的方法
# 创建狗和猫的对象
dog = Dog("阿黄")
cat = Cat("咪咪")
dog.speak() # 调用 Dog 类重写后的 speak 方法
cat.speak() # 调用 Cat 类重写后的 speak 方法
# 子类对象同样拥有父类的属性
print(f"这只狗的名字是:{dog.name}")
输出:
小动物 发出了一些声音。
阿黄 说:汪汪汪!
咪咪 说:喵喵喵~
这只狗的名字是:阿黄
示例2:在重写的方法中调用父类方法(使用 super())
有时,我们不想完全抛弃父类方法的功能,而是希望在它基础上进行扩展。这时可以使用 super() 函数。
class Animal:
def __init__(self, name, sound):
self.name = name
self.sound = sound # 动物的基本叫声
def introduce(self):
"""一个介绍自己的方法"""
print(f"我是一只{self.name},我的叫声是'{self.sound}'。")
class Parrot(Animal): # 鹦鹉类
def __init__(self, name, sound, color):
# 先调用父类的 __init__ 方法,完成基本属性的初始化
super().__init__(name, sound)
# 然后添加子类独有的属性
self.color = color # 鹦鹉的颜色
def introduce(self):
"""重写 introduce 方法,并扩展它"""
# 调用父类的 introduce 方法,执行原有的介绍逻辑
super().introduce()
# 然后添加子类特有的介绍内容
print(f"我有漂亮的{self.color}羽毛。")
# 测试
parrot = Parrot("波利", "嘎嘎", "红绿")
parrot.introduce()
输出:
我是一只波利,我的叫声是'嘎嘎'。
我有漂亮的红绿羽毛。
实践练习
练习1:基础重写
- 创建一个父类
Vehicle,包含属性brand(品牌)和方法describe()。describe()方法打印"这是一个 [品牌] 交通工具。"。 - 创建一个子类
Car,继承自Vehicle。 - 在
Car类中重写describe()方法,使其打印"这是一辆 [品牌] 品牌的汽车。"。 - 创建一个
Car对象并调用其describe()方法。
预期输出:
这是一辆 宝马 品牌的汽车。
练习2:使用 super() 扩展方法
- 创建一个父类
BankAccount,包含属性owner(户主)和balance(余额,初始值为0)。包含方法deposit(amount)用于存款。 - 创建一个子类
InterestAccount,继承自BankAccount。它新增一个属性rate(利率,如0.05代表5%)。 - 在
InterestAccount中重写deposit(amount)方法:首先调用父类的deposit方法完成存款,然后计算并加上利息(利息 = 存款额 * 利率),最后打印"存款成功,并计算利息:[利息金额] 元。当前总余额:[总余额] 元。"。 - 创建一个
InterestAccount对象,存款1000元,观察输出。
预期输出:
存款成功,并计算利息:50.0 元。当前总余额:1050.0 元。
练习3:综合应用
- 设计一个
Shape(形状) 类,包含一个方法area()用于计算面积(这里可以先返回0或抛出一个NotImplementedError表示这是一个需要子类实现的方法)。 - 创建子类
Rectangle(矩形),拥有width和height属性,并重写area()方法计算矩形面积。 - 创建子类
Circle(圆),拥有radius属性,并重写area()方法计算圆面积(π * r²,π可用3.14)。 - 分别创建
Rectangle和Circle对象,并打印它们的面积。
常见错误
-
忘记调用父类的
__init__方法:如果子类重写了__init__方法,但没有调用super().__init__(),那么父类中的属性(如示例2中的name和sound)就不会被初始化,导致后续访问时报AttributeError。class Dog(Animal): def __init__(self, name, trick): # 错误!没有调用 super().__init__(name),导致 self.name 未定义 self.trick = trick -
super()调用方法参数传递错误:在调用super().method()时,必须正确传递所需的参数。class B(A): def method(self, x, y): # 错误!父类 method 可能也需要 x 和 y 参数 super().method() # 这样调用可能因参数不足而失败 -
试图在子类中访问父类的私有属性:父类中以双下划线
__开头的属性是私有的,子类无法直接访问。如果父类提供了公共的访问器(getter)方法,应通过该方法访问。class Parent: def __init__(self): self.__secret = 100 # 私有属性 class Child(Parent): def reveal(self): print(self.__secret) # 错误!AttributeError
小结
本课我们学习了面向对象编程中两个紧密相关的重要概念:
- 继承:子类获得父类的属性和方法,实现了代码的复用,并建立了类之间的"是一个(is-a)"关系(例如,狗是一个动物)。
- 方法重写:子类可以重新定义从父类继承来的方法,以实现特定于子类的行为。这允许我们对父类的功能进行定制或覆盖。
super()函数:它是子类中调用父类方法的桥梁,尤其在重写方法时,用于扩展而非完全替换父类功能,保证了代码的连贯性和正确性。
掌握继承和重写,是构建灵活、可维护的大型程序的基石。它让我们的类结构层次分明,逻辑清晰。在下一课,我们将学习多态,它正是建立在继承和重写之上的更强大特性。