首页 热门资讯文章正文

第 5 章:OOP 三大特性(二):继承

热门资讯 2025年08月24日 12:08 1 admin

在 OOP 的三大特性中,继承是实现 “代码复用” 和 “功能扩展” 的核心机制。它模拟了现实世界中 “一般与特殊” 的关系 —— 例如 “人” 是一般概念,“学生”“教师” 是特殊概念,“学生” 和 “教师” 除了拥有 “人” 的共同特征(如姓名、年龄),还拥有各自的特殊特征(如学生的 “学号”、教师的 “工号”)。本章将从继承的基本概念、单继承的实现、方法重写、多继承的规则四个维度,全面讲解 Python 中继承的设计思路与代码实践。

5.1 什么是继承?

第 5 章:OOP 三大特性(二):继承

继承(Inheritance)是指子类(Subclass)从父类(Parent Class,也称基类、超类)中继承属性和方法,同时子类可以添加自己独有的属性和方法,或修改父类已有的方法。这种机制让子类在复用父类代码的基础上,快速实现功能扩展,避免了重复编写相似逻辑。

5.1.1 继承的核心思想:“复用 + 扩展”

我们以 “人” 和 “学生” 的关系为例,理解继承的核心思想:

  • 父类(人):包含所有 “人” 的共同属性(姓名、年龄)和共同方法(吃饭、睡觉);
  • 子类(学生):继承父类的所有属性和方法,同时添加自己的特殊属性(学号、班级)和特殊方法(学习、考试);
  • 复用:学生无需重复定义 “姓名、年龄” 属性和 “吃饭、睡觉” 方法,直接从父类继承;
  • 扩展:学生在父类基础上,新增 “学习” 等独有的行为,实现功能扩展。

示例:无继承的代码冗余问题

若不使用继承,定义 “Person” 类和 “Student” 类时,需要重复编写 “姓名、年龄” 属性和 “吃饭、睡觉” 方法,导致代码冗余:

# 定义“人”类

class Person:

def __init__(self, name, age):

self.name = name

self.age = age

def eat(self):

print(f"{self.name}在吃饭")

def sleep(self):

print(f"{self.name}在睡觉")

# 定义“学生”类(无继承,重复编写父类的属性和方法)

class Student:

def __init__(self, name, age, student_id):

self.name = name # 重复定义:与Person类的name属性一致

self.age = age # 重复定义:与Person类的age属性一致

self.student_id = student_id # 学生独有的属性

# 重复定义:与Person类的eat方法一致

def eat(self):

print(f"{self.name}在吃饭")

# 重复定义:与Person类的sleep方法一致

def sleep(self):

print(f"{self.name}在睡觉")

# 学生独有的方法

def study(self):

print(f"学生{self.name}(学号:{self.student_id})在学习")

# 创建学生对象

student1 = Student("小明", 20, "2023001")

student1.eat() # 输出:小明在吃饭(重复代码)

student1.study() # 输出:学生小明(学号:2023001)在学习

问题分析:Student类中的name、age属性和eat、sleep方法与Person类完全一致,代码重复率高 —— 若后续需要修改 “吃饭” 的逻辑(如添加 “使用筷子” 的描述),需同时修改Person和Student两个类的eat方法,维护成本高。

示例:使用继承解决代码冗余

通过继承,Student类(子类)可直接复用Person类(父类)的属性和方法,只需专注于新增自己的特殊功能:

# 定义父类:Person

class Person:

def __init__(self, name, age):

self.name = name

self.age = age

def eat(self):

print(f"{self.name}在吃饭")

def sleep(self):

print(f"{self.name}在睡觉")

# 定义子类:Student,继承自Person

class Student(Person):

# 子类的初始化方法:新增学生独有的属性student_id

def __init__(self, name, age, student_id):

# 调用父类的初始化方法,复用name和age的初始化逻辑

super().__init__(name, age)

# 新增学生独有的属性

self.student_id = student_id

# 新增学生独有的方法

def study(self):

print(f"学生{self.name}(学号:{self.student_id})在学习")

# 创建学生对象

student1 = Student("小明", 20, "2023001")

# 1. 调用从父类继承的方法

student1.eat() # 输出:小明在吃饭(复用父类方法)

student1.sleep() # 输出:小明在睡觉(复用父类方法)

# 2. 调用子类新增的方法

student1.study() # 输出:学生小明(学号:2023001)在学习

# 3. 访问从父类继承的属性和子类新增的属性

print(f"姓名:{student1.name},年龄:{student1.age},学号:{student1.student_id}")

# 输出:姓名:小明,年龄:20,学号:2023001

运行结果

小明在吃饭

小明在睡觉

学生小明(学号:2023001)在学习

姓名:小明,年龄:20,学号:2023001

继承的优势体现

  • 代码复用:子类无需重复编写父类已有的属性和方法,减少代码冗余;
  • 易于维护:若需修改父类的通用逻辑(如eat方法),只需修改父类代码,所有子类自动继承修改后的逻辑;
  • 功能扩展:子类可在父类基础上新增属性和方法,实现个性化功能。

5.1.2 继承的关键术语

在 Python 继承中,需理解以下关键术语:

  1. 父类(Parent Class):被继承的类,也称 “基类(Base Class)” 或 “超类(Super Class)”,如上述示例中的Person类;
  1. 子类(Subclass):继承父类的类,也称 “派生类(Derived Class)”,如上述示例中的Student类;
  1. 继承关系:子类与父类之间的 “is-a” 关系(即 “子类是父类的一种”)—— 例如 “Student is a Person”(学生是人),“Dog is an Animal”(狗是动物);
  1. 方法重写(Override):子类定义与父类同名的方法,覆盖父类的方法逻辑(后续 5.3 节详细讲解);
  1. super () 函数:子类中用于调用父类方法的函数,如super().__init__(name, age)调用父类的初始化方法(后续 5.2.2 节详细讲解)。

5.2 Python 的继承语法

Python 支持单继承(子类只继承一个父类)和多继承(子类继承多个父类),其中单继承是最常用的场景,语法简洁且逻辑清晰。

5.2.1 单继承:子类只继承一个父类

单继承的语法格式如下:

class 子类名(父类名):

"""子类的文档字符串"""

# 子类的初始化方法(可选,若需新增属性则定义)

def __init__(self, 父类参数, 子类新增参数):

# 调用父类的初始化方法,初始化父类属性

super().__init__(父类参数)

# 初始化子类新增的属性

self.子类新增属性 = 子类新增参数

# 子类新增的方法(可选)

def 子类方法名(self, 参数...):

pass

# 子类重写的父类方法(可选,后续5.3节讲解)

def 父类方法名(self, 参数...):

pass

语法说明

  1. 继承声明:子类名后的括号内填写 “父类名”,表示子类继承该父类(如class Student(Person):表示Student继承Person);
  1. super () 函数:用于在子类中调用父类的方法,最常用的场景是在子类的__init__中调用父类的__init__,初始化父类的属性(避免重复编写初始化逻辑);
  1. 子类的组成:子类可以包含 “新增的属性”“新增的方法”“重写的父类方法”,也可以什么都不包含(仅继承父类的所有成员)。

示例:单继承的完整实现(Person→Student→CollegeStudent)

继承支持 “多层继承”—— 子类可以再被其他类继承,形成继承链。例如:Person是父类,Student继承Person,CollegeStudent(大学生)继承Student,CollegeStudent会同时拥有Person和Student的所有属性和方法。

# 第一层父类:Person(人)

class Person:

def __init__(self, name, age):

self.name = name

self.age = age

def introduce(self):

print(f"大家好,我是{self.name},今年{self.age}岁")

# 第二层子类:Student(学生),继承Person

class Student(Person):

def __init__(self, name, age, student_id):

super().__init__(name, age) # 调用Person的__init__

self.student_id = student_id # 新增学号属性

# 新增学习方法

def study(self):

print(f"学生{self.name}(学号:{self.student_id})在学习")

# 第三层子类:CollegeStudent(大学生),继承Student

class CollegeStudent(Student):

def __init__(self, name, age, student_id, major):

super().__init__(name, age, student_id) # 调用Student的__init__

self.major = major # 新增专业属性

# 新增参加社团方法

def join_club(self, club_name):

print(f"大学生{self.name}(专业:{self.major})加入了{club_name}社团")

# 创建大学生对象

cs1 = CollegeStudent("小明", 20, "2023001", "计算机科学")

# 1. 调用Person类的方法(多层继承)

cs1.introduce() # 输出:大家好,我是小明,今年20岁

# 2. 调用Student类的方法(多层继承)

cs1.study() # 输出:学生小明(学号:2023001)在学习

# 3. 调用CollegeStudent类的新增方法

cs1.join_club("编程社") # 输出:大学生小明(专业:计算机科学)加入了编程社

# 4. 访问所有层级的属性

print(f"姓名:{cs1.name}(Person),年龄:{cs1.age}(Person),")

print(f"学号:{cs1.student_id}(Student),专业:{cs1.major}(CollegeStudent)")

运行结果

大家好,我是小明,今年20岁

学生小明(学号:2023001)在学习

大学生小明(专业:计算机科学)加入了编程社

姓名:小明(Person),年龄:20(Person),

学号:2023001(Student),专业:计算机科学(CollegeStudent)

多层继承的规则:子类会继承 “继承链上所有父类” 的属性和方法,调用父类方法时,super()会沿着继承链向上查找最近的父类方法(如CollegeStudent的super().__init__会先调用Student的__init__,Student的super().__init__再调用Person的__init__)。

5.2.2 父类方法的调用:super () 函数的用法

super()函数是 Python 继承中非常重要的工具,它的主要作用是在子类中调用父类的方法,避免硬编码父类名(如直接写Person.__init__(self, name, age)),提高代码的灵活性和可维护性。

1. super () 的基本用法:调用父类的__init__方法

在子类的__init__方法中,super().__init__(参数)会调用父类的__init__方法,初始化父类的属性。这是super()最常用的场景。

示例

class Person:

def __init__(self, name, age):

self.name = name

self.age = age

class Student(Person):

def __init__(self, name, age, student_id):

# 方式1:使用super()调用父类__init__(推荐)

super().__init__(name, age)

# 方式2:硬编码父类名调用(不推荐,若父类名修改需同步修改此处)

# Person.__init__(self, name, age)

self.student_id = student_id

student1 = Student("小明", 20, "2023001")

print(f"姓名:{student1.name},年龄:{student1.age},学号:{student1.student_id}")

# 输出:姓名:小明,年龄:20,学号:2023001

为什么推荐 super () 而非硬编码父类名

若后续将Student的父类从Person改为Employee(如需求变更),使用super()的代码无需修改,而硬编码Person.__init__的代码需要同步修改为Employee.__init__,维护成本更高。

2. super () 的进阶用法:调用父类的其他方法

除了__init__方法,super()还可以调用父类的其他实例方法,适用于 “子类方法需要复用父类方法逻辑,并在此基础上扩展” 的场景。

示例:子类Student的introduce方法复用父类Person的introduce逻辑,并新增学号信息:

class Person:

def __init__(self, name, age):

self.name = name

self.age = age

def introduce(self):

# 父类的自我介绍逻辑

return f"大家好,我是{self.name},今年{self.age}岁"

class Student(Person):

def __init__(self, name, age, student_id):

super().__init__(name, age)

self.student_id = student_id

def introduce(self):

# 1. 调用父类的introduce方法,复用基础逻辑

parent_intro = super().introduce()

# 2. 新增子类的特殊逻辑(添加学号信息)

student_intro = f"{parent_intro},我的学号是{self.student_id}"

print(student_intro)

# 创建学生对象并调用方法

student1 = Student("小明", 20, "2023001")

student1.introduce() # 输出:大家好,我是小明,今年20岁,我的学号是2023001

运行结果

大家好,我是小明,今年20岁,我的学号是2023001

逻辑分析:子类Student的introduce方法没有完全重写父类逻辑,而是通过super().introduce()获取父类的自我介绍字符串,再拼接学号信息,实现 “复用 + 扩展” 的效果 —— 这比完全重写父类方法更高效,也更符合继承的设计思想。

3. super () 在多层继承中的行为

在多层继承中,super()会沿着 “方法解析顺序(MRO)” 向上查找父类方法(MRO 后续 5.4.2 节详细讲解),确保每个父类的方法只被调用一次。

示例

class A:

def show(self):

print("A类的show方法")

class B(A):

def show(self):

print("B类的show方法(开始)")

super().show() # 调用父类A的show方法

print("B类的show方法(结束)")

class C(B):

def show(self):

print("C类的show方法(开始)")

super().show() # 调用父类B的show方法

print("C类的show方法(结束)")

# 创建C类对象并调用show方法

c1 =</doubaocanvas>

C()

c1.show()

**运行结果**:

C 类的 show 方法(开始)

B 类的 show 方法(开始)

A 类的 show 方法

B 类的 show 方法(结束)

C 类的 show 方法(结束)

**逻辑分析**:调用`c1.show()`时,执行流程如下:

1. 先执行`C`类的`show`方法,打印“C类的show方法(开始)”;

2. 调用`super().show()`,沿着MRO找到`B`类的`show`方法,执行该方法,打印“B类的show方法(开始)”;

3. 在`B`类的`show`方法中调用`super().show()`,沿着MRO找到`A`类的`show`方法,执行该方法,打印“A类的show方法”;

4. `A`类的`show`方法执行完毕,回到`B`类,打印“B类的show方法(结束)”;

5. `B`类的`show`方法执行完毕,回到`C`类,打印“C类的show方法(结束)”。

可见,`super()`会严格按照MRO顺序查找父类方法,确保多层继承中的方法调用逻辑清晰且无重复。

## 5.3 方法重写(Override)

在继承关系中,子类可能需要修改父类已有的方法逻辑(如父类`Person`的`work`方法默认“上班”,子类`Student`的`work`方法应改为“学习”),这种“子类定义与父类同名方法,覆盖父类方法逻辑”的操作称为**方法重写**。

### 5.3.1 方法重写的场景:子类需要个性化行为

当父类的方法逻辑无法满足子类的需求时,就需要进行方法重写。例如:

- 父类`Animal`的`make_sound`方法默认“发出声音”,子类`Dog`的`make_sound`需重写为“汪汪叫”,子类`Cat`的`make_sound`需重写为“喵喵叫”;

- 父类`Vehicle`的`run`方法默认“以普通速度行驶”,子类`SportsCar`的`run`需重写为“以高速行驶”。

#### 示例:方法重写的基础实现(Animal→Dog/Cat)

```python

# 父类:Animal(动物)

class Animal:

def __init__(self, name):

self.name = name

# 父类的通用方法:发出声音(逻辑较笼统)

def make_sound(self):

print(f"{self.name}发出了声音")

# 子类1:Dog(狗),重写make_sound方法

class Dog(Animal):

def make_sound(self):

# 重写为“汪汪叫”的逻辑

print(f"{self.name}汪汪叫:汪!汪!")

# 子类2:Cat(猫),重写make_sound方法

class Cat(Animal):

def make_sound(self):

# 重写为“喵喵叫”的逻辑

print(f"{self.name}喵喵叫:喵~ 喵~")

# 创建对象并调用方法

dog1 = Dog("小黑")

cat1 = Cat("小白")

dog1.make_sound() # 输出:小黑汪汪叫:汪!汪!(调用子类重写的方法)

cat1.make_sound() # 输出:小白喵喵叫:喵~ 喵~(调用子类重写的方法)

# 父类对象调用方法(仍使用父类逻辑)

animal1 = Animal("不知名动物")

animal1.make_sound() # 输出:不知名动物发出了声音

运行结果

小黑汪汪叫:汪!汪!

小白喵喵叫:喵~ 喵~

不知名动物发出了声音

方法重写的核心规则

  1. 方法名必须完全一致:子类重写的方法名需与父类方法名完全相同(包括大小写),否则无法覆盖;
  1. 参数列表需兼容:子类方法的参数列表(个数、类型)应与父类方法兼容(推荐完全一致),避免调用时出现参数不匹配的错误;
  1. 返回值类型需兼容:子类方法的返回值类型应与父类方法一致或为其子类型,确保外部调用时无需修改接收返回值的逻辑。

5.3.2 重写与 super () 结合:复用父类逻辑并扩展

方法重写并非一定要完全抛弃父类逻辑,很多场景下,子类需要 “复用父类方法的部分逻辑,再添加自己的特殊逻辑”,此时可通过super()调用父类方法,实现 “复用 + 扩展”。

示例:重写introduce方法,复用父类逻辑

# 父类:Person

class Person:

def __init__(self, name, age):

self.name = name

self.age = age

def introduce(self):

# 父类的基础自我介绍逻辑

print(f"大家好,我是{self.name},今年{self.age}岁")

# 子类:Student,重写introduce方法

class Student(Person):

def __init__(self, name, age, student_id):

super().__init__(name, age)

self.student_id = student_id

def introduce(self):

# 1. 复用父类的基础逻辑

super().introduce()

# 2. 新增子类的特殊逻辑(添加学号信息)

print(f"我的学号是{self.student_id},很高兴成为一名学生!")

# 子类:Teacher,重写introduce方法

class Teacher(Person):

def __init__(self, name, age, subject):

super().__init__(name, age)

self.subject = subject

def introduce(self):

# 1. 复用父类的基础逻辑

super().introduce()

# 2. 新增子类的特殊逻辑(添加教授科目信息)

print(f"我教授{self.subject},希望和大家共同进步!")

# 创建对象并调用方法

student1 = Student("小明", 20, "2023001")

teacher1 = Teacher("李老师", 35, "数学")

print("=== 学生自我介绍 ===")

student1.introduce()

print("\n=== 老师自我介绍 ===")

teacher1.introduce()

运行结果

=== 学生自我介绍 ===

大家好,我是小明,今年20岁

我的学号是2023001,很高兴成为一名学生!

=== 老师自我介绍 ===

大家好,我是李老师,今年35岁

我教授数学,希望和大家共同进步!

优势分析

  • 避免重复代码:父类的 “姓名、年龄” 介绍逻辑只需编写一次,子类无需重复编写;
  • 逻辑统一:若后续修改父类的基础介绍逻辑(如添加 “性别” 信息),所有子类的自我介绍都会自动包含该信息,无需逐个修改子类。

5.3.3 方法重写的常见错误与避免

错误案例 1:方法名拼写错误,导致未成功重写

class Animal:

def make_sound(self):

print("发出声音")

class Dog(Animal):

# 错误:方法名拼写错误(make_sound→make_sounds),未重写父类方法

def make_sounds(self):

print("汪汪叫")

dog1 = Dog()

dog1.make_sound() # 输出:发出声音(调用父类方法,未触发重写)

解决方法:重写前仔细核对父类方法名,确保完全一致;大型项目中可使用 IDE 的 “重写方法” 功能(如 PyCharm 中按Ctrl+O选择要重写的方法),避免拼写错误。

错误案例 2:参数列表不兼容,导致调用报错

class Person:

# 父类方法:2个参数(self+message)

def speak(self, message):

print(f"说:{message}")

class Student(Person):

# 错误:子类方法参数个数与父类不一致(少了message参数)

def speak(self):

print("学生说:大家好")

student1 = Student()

# 调用时传入参数,与子类方法参数个数不匹配,报错

try:

student1.speak("我是小明")

except TypeError as e:

print("调用报错:", e) # 输出:调用报错:speak() takes 1 positional argument but 2 were given

解决方法:确保子类重写方法的参数列表与父类兼容(推荐完全一致),若子类需要额外参数,可通过 “默认参数” 实现,如def speak(self, message, extra=""):,既兼容父类调用,又支持子类扩展。

5.4 多继承与 MRO(方法解析顺序)

Python 不仅支持单继承,还支持多继承—— 子类可以同时继承多个父类,从而拥有所有父类的属性和方法。多继承能实现更灵活的功能组合,但也会带来 “方法名冲突” 等问题,因此需要通过 “方法解析顺序(MRO)” 来规范方法的调用逻辑。

5.4.1 多继承语法:子类继承多个父类

多继承的语法格式如下:

class 子类名(父类1, 父类2, 父类3, ...):

"""子类的文档字符串"""

def __init__(self, 父类1参数, 父类2参数, ...):

# 调用父类的初始化方法(需显式调用,super()默认只调用第一个父类)

父类1.__init__(self, 父类1参数)

父类2.__init__(self, 父类2参数)

# ... 其他父类的初始化

# 子类的方法(可重写父类方法)

pass

示例:多继承的基础实现(Student 继承 Person 和 StudyAbility)

假设我们有两个独立的父类:Person(包含姓名、年龄属性)和StudyAbility(包含学习相关方法),Student类需要同时拥有 “人的属性” 和 “学习能力”,此时可通过多继承实现:

# 父类1:Person(人,包含基础属性)

class Person:

def __init__(self, name, age):

self.name = name

self.age = age

def introduce(self):

print(f"姓名:{self.name},年龄:{self.age}岁")

# 父类2:StudyAbility(学习能力,包含学习方法)

class StudyAbility:

def study(self, subject):

print(f"正在学习{subject}")

def do_homework(self):

print("正在做家庭作业")

# 子类:Student,同时继承Person和StudyAbility

class Student(Person, StudyAbility):

def __init__(self, name, age, student_id):

# 调用父类1(Person)的初始化方法

Person.__init__(self, name, age)

# 调用父类2(StudyAbility)的初始化方法(若父类2无__init__,可省略)

# StudyAbility.__init__(self)

self.student_id = student_id # 子类新增属性

# 创建Student对象

student1 = Student("小明", 20, "2023001")

# 1. 调用父类1(Person)的方法

student1.introduce() # 输出:姓名:小明,年龄:20岁

# 2. 调用父类2(StudyAbility)的方法

student1.study("数学") # 输出:正在学习数学

student1.do_homework() # 输出:正在做家庭作业

# 3. 访问子类新增属性

print(f"学号:{student1.student_id}") # 输出:学号:2023001

运行结果

姓名:小明,年龄:20岁

正在学习数学

正在做家庭作业

学号:2023001

多继承的优势:实现 “功能组合”—— 子类无需重复编写多个父类的功能,只需通过继承将多个独立的功能模块组合在一起(如Student=“人”+“学习能力”)。

5.4.2 MRO(方法解析顺序):解决多继承的方法冲突

当多继承的多个父类拥有同名方法时,子类调用该方法会优先执行哪个父类的逻辑?这就需要通过 “方法解析顺序(Method Resolution Order,简称 MRO)” 来确定 ——Python 会按照特定的顺序(C3 线性化算法)遍历父类,找到第一个匹配的方法并执行。

1. 查看 MRO:通过__mro__属性或mro()方法

每个类都有__mro__属性和mro()方法,用于查看该类的方法解析顺序,返回结果为 “类的元组”,顺序从左到右,优先级依次降低。

示例:查看多继承的 MRO

class A:

def show(self):

print("A类的show方法")

class B(A):

def show(self):

print("B类的show方法")

class C(A):

def show(self):

print("C类的show方法")

# 子类D同时继承B和C,父类顺序为(B, C)

class D(B, C):

pass

# 查看D类的MRO

print("D类的MRO(__mro__属性):", D.__mro__)

print("D类的MRO(mro()方法):", D.mro())

# 创建D类对象并调用show方法(按MRO顺序执行第一个匹配的方法)

d1 = D()

d1.show() # 输出:B类的show方法(因为MRO中B在C前面)

运行结果

D类的MRO(__mro__属性): (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

D类的MRO(mro()方法): [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

B类的show方法

MRO 顺序分析

  • D类的 MRO 顺序为:D → B → C → A → object(object是所有 Python 类的顶层父类);
  • 调用d1.show()时,Python 按 MRO 顺序查找show方法:先在D类中查找(无),再在B类中查找(有),因此执行B类的show方法,不再继续查找C和A类的方法。

2. MRO 的核心规则(C3 线性化算法)

Python 的 MRO 遵循以下核心规则,确保方法查找逻辑清晰且无矛盾:

  1. 子类优先于父类:MRO 中,子类始终在父类前面(如D在B、C前面);
  1. 父类的声明顺序优先:多继承时,父类的声明顺序决定优先级(如D(B, C)中B在C前面,MRO 中B也在C前面);
  1. 祖父类优先于曾祖父类:多层继承中,更接近子类的父类优先(如B和C都继承A,MRO 中B、C在A前面);
  1. 避免循环依赖:若继承关系出现循环(如A继承B,B继承A),Python 会直接报错,禁止这种非法继承。

3. 多继承的方法冲突解决:显式指定父类或重写方法

当多继承的父类存在同名方法,且子类需要调用非 MRO 优先的父类方法时,可通过 “显式指定父类名” 的方式调用,或在子类中重写方法,自定义逻辑。

示例 1:显式调用指定父类的方法

class B:

def show(self):

print("B类的show方法")

class C:

def show(self):

print("C类的show方法")

class D(B, C):

def call_parent_show(self):

# 显式调用B类的show方法

B.show(self)

# 显式调用C类的show方法

C.show(self)

d1 = D()

d1.call_parent_show()

运行结果

B类的show方法

C类的show方法

示例 2:子类重写方法,整合多个父类的逻辑

class B:

def show(self):

return "B类的</doubaocanvas>

内容 ")

class C:

def show (self):

return "C 类的内容"

class D(B, C):

def show(self):

重写 show 方法,整合 B 类和 C 类的逻辑

b_content = B.show (self) # 调用 B 类的 show 方法

c_content = C.show (self) # 调用 C 类的 show 方法

return f"D 类整合结果:{b_content} + {c_content}"

d1 = D ()

print (d1.show ()) # 输出:D 类整合结果:B 类的内容 + C 类的内容

**运行结果**:

D 类整合结果:B 类的内容 + C 类的内容

### 5.4.3 多继承的注意事项:避免“菱形继承”陷阱

多继承虽然灵活,但也存在风险,其中最典型的是“菱形继承”(也称“钻石继承”)——子类的两个父类同时继承自同一个祖父类,形成“菱形”的继承结构。这种结构可能导致祖父类的方法被重复调用(若未使用`super()`),或方法查找逻辑混乱。

#### 1. 菱形继承的结构示例

A(祖父类)

/

B C(父类)

\ /

D(子类)

#### 2. 未使用super()的问题:祖父类方法重复调用

若子类的父类在初始化时未使用`super()`,而是硬编码调用祖父类的方法,会导致祖父类的方法被重复调用,造成逻辑错误或资源浪费。

**错误案例**:

```python

class A:

def __init__(self):

print("A类的__init__方法被调用")

class B(A):

def __init__(self):

# 硬编码调用A的__init__(未使用super())

A.__init__(self)

print("B类的__init__方法被调用")

class C(A):

def __init__(self):

# 硬编码调用A的__init__(未使用super())

A.__init__(self)

print("C类的__init__方法被调用")

class D(B, C):

def __init__(self):

# 调用B和C的__init__

B.__init__(self)

C.__init__(self)

print("D类的__init__方法被调用")

# 创建D类对象(A的__init__被调用2次,重复初始化)

d1 = D()

运行结果

A类的__init__方法被调用

B类的__init__方法被调用

A类的__init__方法被调用

C类的__init__方法被调用

D类的__init__方法被调用

问题分析:D类的__init__调用B和C的__init__,而B和C的__init__又分别调用A的__init__,导致A的__init__被重复调用 2 次 —— 若A的__init__中有资源初始化(如打开文件、创建数据库连接),会造成资源浪费或冲突。

3. 使用 super () 解决菱形继承问题

在菱形继承中,所有父类的__init__方法都应使用super()调用,Python 会根据 MRO 顺序自动确保祖父类的方法只被调用一次。

正确案例

class A:

def __init__(self):

super().__init__() # 使用super()调用下一个父类(按MRO)

print("A类的__init__方法被调用")

class B(A):

def __init__(self):

super().__init__() # 使用super()调用A的__init__

print("B类的__init__方法被调用")

class C(A):

def __init__(self):

super().__init__() # 使用super()调用A的__init__(但MRO确保只调用一次)

print("C类的__init__方法被调用")

class D(B, C):

def __init__(self):

super().__init__() # 使用super()调用B的__init__

print("D类的__init__方法被调用")

# 查看D类的MRO

print("D类的MRO:", D.__mro__)

# 创建D类对象(A的__init__只被调用1次)

d1 = D()

运行结果

D类的MRO: (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

A类的__init__方法被调用

C类的__init__方法被调用

B类的__init__方法被调用

D类的__init__方法被调用

逻辑分析

  • D类的 MRO 顺序为D→B→C→A→object;
  • 调用D.__init__时,super()调用B.__init__;
  • B.__init__的super()调用C.__init__(按 MRO,B的下一个父类是C);
  • C.__init__的super()调用A.__init__;
  • A.__init__的super()调用object.__init__(无逻辑);
  • 最终A的__init__只被调用 1 次,避免重复初始化。

多继承的使用建议

  1. 优先使用单继承:单继承逻辑清晰,不易出现方法冲突和 MRO 问题,大多数场景下单继承足以满足需求;
  1. 谨慎使用多继承:仅在 “功能组合” 场景下使用多继承(如子类需要整合多个独立父类的功能),避免过度设计;
  1. 避免菱形继承:尽量减少复杂的继承结构,若无法避免,确保所有父类都使用super()调用方法,遵循 MRO 规则;
  1. 使用 Mixin 类优化多继承:将通用功能封装为 “Mixin 类”(如LogMixin包含日志功能、AuthMixin包含认证功能),子类通过继承 Mixin 类获取功能,避免主继承链过于复杂(Mixin 类通常不单独实例化,只用于被继承)。

示例:Mixin 类的使用(为 Student 添加日志功能)

# Mixin类:日志功能(仅用于被继承,不单独实例化)

class LogMixin:

def log(self, message):

# 模拟日志记录:打印时间和消息

import datetime

current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

print(f"[{current_time}] 日志:{message}")

# 父类:Person

class Person:

def __init__(self, name):

self.name = name

# 子类:Student,继承Person(主父类)和LogMixin(功能Mixin类)

class Student(Person, LogMixin):

def study(self, subject):

# 调用Mixin类的log方法

self.log(f"{self.name}开始学习{subject}")

print(f"{self.name}正在学习{subject}")

# 创建Student对象

student1 = Student("小明")

student1.study("数学") # 同时拥有Person的属性和LogMixin的日志功能

运行结果

[2025-08-24 15:30:00] 日志:小明开始学习数学

小明正在学习数学

5.5 本章实战:基于 “Person” 父类,创建 “Student” 和 “Teacher” 子类,重写 “工作” 方法

本实战将综合继承、方法重写、super () 调用等核心知识点,设计一个 “人员管理” 的基础案例,具体需求如下:

实战需求

  1. 定义Person父类,包含以下成员:
    • 实例属性:name(姓名)、age(年龄);
    • 实例方法:
      • __init__(self, name, age):初始化姓名和年龄,参数需校验(姓名非空、年龄 0-150);
      • work(self):通用 “工作” 方法,打印 “{姓名} 正在工作”;
      • introduce(self):自我介绍方法,打印 “大家好,我是 {姓名},今年 {年龄} 岁”。
  1. 定义Student子类(继承Person),包含以下成员:
    • 新增实例属性:student_id(学号)、grade(年级);
    • 重写方法:
      • __init__(self, name, age, student_id, grade):调用父类__init__,初始化学号和年级(学号非空、年级非空);
      • work(self):重写为 “学习” 逻辑,打印 “{姓名}(学号:{学号})正在学习,年级:{年级}”;
    • 新增方法:do_homework(self, subject):打印 “{姓名} 正在做 {subject} 作业”。
  1. 定义Teacher子类(继承Person),包含以下成员:
    • 新增实例属性:teacher_id(教师编号)、subject(教授科目);
    • 重写方法:
      • __init__(self, name, age, teacher_id, subject):调用父类__init__,初始化教师编号和教授科目(编号非空、科目非空);
      • work(self):重写为 “教学” 逻辑,打印 “{姓名}(教师编号:{教师编号})正在教授 {科目}”;
    • 新增方法:correct_homework(self, student_name):打印 “{姓名} 正在批改 {student_name} 的作业”。
  1. 完成以下操作:
    • 创建 1 个Student对象(姓名 “小明”,年龄 20,学号 “2023001”,年级 “大二”);
    • 创建 1 个Teacher对象(姓名 “李老师”,年龄 35,教师编号 “T202301”,科目 “数学”);
    • 分别调用两个对象的introduce()、work()方法;
    • 调用Student的do_homework()方法(科目 “数学”);
    • 调用Teacher的correct_homework()方法(学生姓名 “小明”);
    • 验证父类Person的work()方法(创建Person对象并调用)。

实战代码实现

class Person:

def __init__(self, name, age):

# 校验姓名:非空字符串

if not isinstance(name, str) or len(name.strip()) == 0:

raise ValueError("姓名必须是非空字符串")

# 校验年龄:0-150的整数

if not isinstance(age, int) or age < 0 or age > 150:

raise ValueError("年龄必须是0-150之间的整数")

self.name = name.strip()

self.age = age

def work(self):

# 父类通用工作方法

print(f"{self.name}正在工作")

def introduce(self):

# 自我介绍方法

print(f"大家好,我是{self.name},今年{self.age}岁")

class Student(Person):

def __init__(self, name, age, student_id, grade):

# 调用父类__init__,初始化姓名和年龄

super().__init__(name, age)

# 校验学号:非空字符串

if not isinstance(student_id, str) or len(student_id.strip()) == 0:

raise ValueError("学号必须是非空字符串")

# 校验年级:非空字符串

if not isinstance(grade, str) or len(grade.strip()) == 0:

raise ValueError("年级必须是非空字符串")

self.student_id = student_id.strip()

self.grade = grade.strip()

def work(self):

# 重写work方法:学生的“工作”是学习

print(f"{self.name}(学号:{self.student_id})正在学习,年级:{self.grade}")

def do_homework(self, subject):

# 新增方法:做家庭作业

if not isinstance(subject, str) or len(subject.strip()) == 0:

print("科目不能为空")

return

print(f"{self.name}正在做{subject.strip()}作业")

class Teacher(Person):

def __init__(self, name, age, teacher_id, subject):

# 调用父类__init__,初始化姓名和年龄

super().__init__(name, age)

# 校验教师编号:非空字符串

if not isinstance(teacher_id, str) or len(teacher_id.strip()) == 0:

raise ValueError("教师编号必须是非空字符串")

# 校验教授科目:非空字符串

if not isinstance(subject, str) or len(subject.strip()) == 0:

raise ValueError("教授科目必须是非空字符串")

self.teacher_id = teacher_id.strip()

self.subject = subject.strip()

def work(self):

# 重写work方法:教师的“工作”是教学

print(f"{self.name}(教师编号:{self.teacher_id})正在教授{self.subject}")

def correct_homework(self, student_name):

# 新增方法:批改作业

if not isinstance(student_name, str) or len(student_name.strip()) == 0:

print("学生姓名不能为空")

return

print(f"{self.name}正在批改{student_name.strip()}的作业")

# 1. 创建Student对象并调用方法

print("=== 学生操作 ===")

try:

student1 = Student("小明 ", 20, " 2023001 ", " 大二 ") # 带空格,strip()处理

student1.introduce() # 调用父类方法

student1.work() # 调用重写的方法

student1.do_homework("数学") # 调用新增方法

except ValueError as e:

print("创建学生对象失败:", e)

# 2. 创建Teacher对象并调用方法

print("\n=== 教师操作 ===")

try:

teacher1 = Teacher("李老师", 35, "T202301", "数学")

teacher1.introduce() # 调用父类方法

teacher1.work() # 调用重写的方法

teacher1.correct_homework("小明") # 调用新增方法

except ValueError as e:

print("创建教师对象失败:", e)

# 3. 创建Person对象并调用方法

print("\n=== 父类Person操作 ===")

try:

person1 = Person("王工", 40)

person1.introduce()

person1.work()

except ValueError as e:

print("创建Person对象失败:", e)

# 4. 测试参数校验(创建非法对象)

print("\n=== 非法对象测试 ===")

try:

Student("", 20, "2023002", "大二") # 姓名为空

except ValueError as e:

print("创建学生失败:", e)

try:

Teacher("张老师", 200, "T202302", "英语") # 年龄超出范围

except ValueError as e:

print("创建教师失败:", e)

实战运行结果

=== 学生操作 ===

大家好,我是小明,今年20岁

小明(学号:2023001)正在学习,年级:大二

小明正在做数学作业

=== 教师操作 ===

大家好,我是李老师,今年35岁

李老师(教师编号:T202301)正在教授数学

李老师正在批改小明的作业

=== 父类Person操作 ===

大家好,我是王工,今年40岁

王工正在工作

=== 非法对象测试 ===

创建学生失败:姓名必须是非空字符串

创建教师失败:年龄必须是0-150之间的整数

实战总结

通过本次实战,我们巩固了以下核心知识点:

  1. 继承的实现:子类通过class 子类名(父类名)继承父类,自动拥有父类的属性和方法;
  1. super () 的应用:子类__init__中通过super().__init__调用父类初始化方法,复用父类逻辑,避免代码重复;
  1. 方法重写:子类定义与父类同名的work方法,覆盖父类逻辑,实现子类的个性化行为;
  1. 参数校验:在__init__中添加参数合法性校验,确保创建的对象有效,增强代码健壮性;
  1. 功能扩展:子类在继承父类的基础上,新增属性(

发表评论

泰日号Copyright Your WebSite.Some Rights Reserved. 网站地图 备案号:川ICP备66666666号 Z-BlogPHP强力驱动