类的继承
深入理解 Python 类的继承机制,包括单继承、多继承、方法重写、super()函数的使用以及继承中的初始化过程
类的继承
概述
继承是面向对象编程的三大特性之一,它允许一个类(子类)从另一个类(父类)获取属性和方法。通过继承,我们可以实现代码复用,建立类之间的层次关系,并支持多态性。Python 支持单继承和多继承,为面向对象设计提供了强大的工具。
学习目标
通过本章学习,你将能够:
- 理解继承的基本概念和语法
- 掌握方法重写和 super()函数的使用
- 了解继承中的初始化过程
- 理解访问控制在继承中的表现
- 掌握多继承的概念和方法解析顺序(MRO)
- 能够设计合理的类继承结构
前置知识
- Python 类的定义和基本使用
- 实例属性和类属性的区别
- 方法的定义和调用
- 面向对象编程基础概念
详细内容
基本概念
继承允许子类从父类获取特征(属性和方法),实现代码复用和层次化设计。
基本语法
class 子类名(父类名):
# # 子类的定义
pass
## 多继承语法
class 子类名(父类 1, 父类 2, ...):
# # 子类的定义
pass
简单继承示例
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print(f"{self.name} 发出声音")
def move(self):
print(f"{self.name} 在移动")
class Dog(Animal):
def speak(self):
print(f"{self.name} 汪汪叫")
class Cat(Animal):
def speak(self):
print(f"{self.name} 喵喵叫")
## 使用示例
dog = Dog("小黑")
cat = Cat("小白")
dog.speak() # 小黑 汪汪叫
dog.move() # 小黑 在移动
cat.speak() # 小白 喵喵叫
cat.move() # 小白 在移动
继承中的访问控制
在继承关系中,访问控制规则依然有效:
class Parent:
def __init__(self):
self.public_attr = "公有属性"
self._protected_attr = "受保护属性"
self.__private_attr = "私有属性"
def public_method(self):
return "公有方法"
def _protected_method(self):
return "受保护方法"
def __private_method(self):
return "私有方法"
def access_private(self):
return self.__private_method() # 类内部可以访问私有方法
class Child(Parent):
def test_access(self):
print(self.public_attr) # ✓ 可以访问
print(self._protected_attr) # ✓ 可以访问
# # print(self.__private_attr) # ✗ 不能直接访问
self.public_method() # ✓ 可以调用
self._protected_method() # ✓ 可以调用
# # self.__private_method() # ✗ 不能直接调用
# # 通过父类的公有方法间接访问私有成员
print(self.access_private()) # ✓ 可以调用
## 使用示例
child = Child()
child.test_access()
方法重写
子类可以重写父类的方法,提供自己的实现:
class Vehicle:
def __init__(self, brand):
self.brand = brand
def start(self):
print(f"{self.brand} 启动了")
def stop(self):
print(f"{self.brand} 停止了")
class Car(Vehicle):
def start(self):
print(f"{self.brand} 汽车点火启动")
print("检查安全带")
print("启动引擎")
class Bicycle(Vehicle):
def start(self):
print(f"{self.brand} 自行车开始骑行")
print("踩踏板")
## 使用示例
car = Car("奔驰")
bike = Bicycle("捷安特")
car.start() # 重写的方法
car.stop() # 继承的方法
bike.start() # 重写的方法
super()函数的使用
super()函数用于调用父类的方法,特别是在方法重写时:
class Animal:
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
print(f"我是 {self.name},{self.age} 岁")
class Dog(Animal):
def __init__(self, name, age, breed):
super().__init__(name, age) # 调用父类的__init__
self.breed = breed
def introduce(self):
super().introduce() # 调用父类的 introduce 方法
print(f"我是 {self.breed} 品种的狗")
def bark(self):
print(f"{self.name} 在汪汪叫")
## 使用示例
dog = Dog("旺财", 3, "金毛")
dog.introduce()
## 输出:
## 我是 旺财,3 岁
## 我是 金毛 品种的狗
继承中的初始化
理解继承中的初始化过程非常重要:
class Parent:
def __init__(self, value):
self.parent_value = value
print(f"Parent 初始化: {value}")
class Child(Parent):
def __init__(self, value, child_value):
super().__init__(value) # 必须显式调用父类的__init__
self.child_value = child_value
print(f"Child 初始化: {child_value}")
## 正确的初始化
child = Child("父类值", "子类值")
print(child.parent_value) # 父类值
print(child.child_value) # 子类值
如果子类没有定义__init__方法,会自动调用父类的__init__:
class Parent:
def __init__(self):
self.value = "来自父类"
print("父类初始化")
class Child(Parent):
pass # 没有定义__init__
## 会自动调用父类的__init__
child = Child() # 输出: 父类初始化
print(child.value) # 来自父类
多继承
Python 支持多继承,但需要注意方法解析顺序(MRO):
class Flyable:
def fly(self):
print("我可以飞行")
class Swimmable:
def swim(self):
print("我可以游泳")
class Duck(Animal, Flyable, Swimmable):
def __init__(self, name):
super().__init__(name)
def speak(self):
print(f"{self.name} 嘎嘎叫")
## 使用示例
duck = Duck("唐老鸭")
duck.speak() # 唐老鸭 嘎嘎叫
duck.fly() # 我可以飞行
duck.swim() # 我可以游泳
duck.move() # 唐老鸭 在移动(继承自 Animal)
## 查看方法解析顺序
print(Duck.__mro__)
## (<class '__main__.Duck'>, <class '__main__.Animal'>,
## <class '__main__.Flyable'>, <class '__main__.Swimmable'>,
## <class 'object'>)
继承相关的特殊属性
class A:
pass
class B(A):
pass
class C(B):
pass
## 查看继承关系
print(C.__bases__) # 直接父类: (<class '__main__.B'>,)
print(C.__mro__) # 方法解析顺序
print(C.mro()) # 同上
print(A.__subclasses__()) # A 的子类: [<class '__main__.B'>]
## 实例检查
c = C()
print(isinstance(c, C)) # True
print(isinstance(c, B)) # True
print(isinstance(c, A)) # True
print(issubclass(C, A)) # True
实际应用案例
案例 1:员工管理系统
class Employee:
"""员工基类"""
def __init__(self, name, employee_id, salary):
self.name = name
self.employee_id = employee_id
self.salary = salary
def get_info(self):
return f"员工: {self.name}, ID: {self.employee_id}, 薪资: {self.salary}"
def calculate_bonus(self):
return self.salary * 0.1 # 基础奖金 10%
class Manager(Employee):
"""经理类"""
def __init__(self, name, employee_id, salary, team_size):
super().__init__(name, employee_id, salary)
self.team_size = team_size
def calculate_bonus(self):
base_bonus = super().calculate_bonus()
team_bonus = self.team_size * 1000 # 每个团队成员 1000 奖金
return base_bonus + team_bonus
def get_info(self):
base_info = super().get_info()
return f"{base_info}, 团队规模: {self.team_size}"
class Developer(Employee):
"""开发者类"""
def __init__(self, name, employee_id, salary, programming_languages):
super().__init__(name, employee_id, salary)
self.programming_languages = programming_languages
def calculate_bonus(self):
base_bonus = super().calculate_bonus()
skill_bonus = len(self.programming_languages) * 500 # 每种语言 500 奖金
return base_bonus + skill_bonus
def get_info(self):
base_info = super().get_info()
languages = ", ".join(self.programming_languages)
return f"{base_info}, 技能: {languages}"
## 使用示例
manager = Manager("张三", "M001", 15000, 5)
developer = Developer("李四", "D001", 12000, ["Python", "Java", "JavaScript"])
print(manager.get_info())
print(f"经理奖金: {manager.calculate_bonus()}")
print()
print(developer.get_info())
print(f"开发者奖金: {developer.calculate_bonus()}")
案例 2:图形绘制系统
import math
class Shape:
"""图形基类"""
def __init__(self, color="black"):
self.color = color
def area(self):
raise NotImplementedError("子类必须实现 area 方法")
def perimeter(self):
raise NotImplementedError("子类必须实现 perimeter 方法")
def describe(self):
return f"这是一个{self.color}色的图形"
class Circle(Shape):
"""圆形类"""
def __init__(self, radius, color="black"):
super().__init__(color)
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
def perimeter(self):
return 2 * math.pi * self.radius
def describe(self):
base_desc = super().describe()
return f"{base_desc},半径为{self.radius}的圆形"
class Rectangle(Shape):
"""矩形类"""
def __init__(self, width, height, color="black"):
super().__init__(color)
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
def describe(self):
base_desc = super().describe()
return f"{base_desc},宽{self.width}高{self.height}的矩形"
class Square(Rectangle):
"""正方形类(继承自矩形)"""
def __init__(self, side, color="black"):
super().__init__(side, side, color) # 正方形的宽高相等
self.side = side
def describe(self):
return f"这是一个{self.color}色的,边长为{self.side}的正方形"
## 使用示例
shapes = [
Circle(5, "红"),
Rectangle(4, 6, "蓝"),
Square(3, "绿")
]
for shape in shapes:
print(shape.describe())
print(f"面积: {shape.area():.2f}")
print(f"周长: {shape.perimeter():.2f}")
print("-" * 30)
注意事项
- 合理使用继承:继承应该表示”是一个”的关系,而不是”有一个”的关系
- 避免过深的继承层次:过深的继承会增加代码复杂性
- 谨慎使用多继承:多继承可能导致钻石问题,需要理解 MRO
- 正确调用父类初始化:在子类的
__init__中记得调用super().__init__() - 遵循里氏替换原则:子类对象应该能够替换父类对象而不影响程序正确性
相关内容
扩展阅读
- Python 官方文档:类的继承
- 《Effective Python》中关于继承的最佳实践
- 面向对象设计原则(SOLID 原则)
- 组合 vs 继承的选择策略
🧭 继承关系示例:Circle 继承 Point
下面是一个简洁的继承示例,展示如何让 Circle 继承自 Point,并进行类型关系检查:
# 基类:点
class Point:
"""二维坐标点"""
def __init__(self, x: float, y: float):
self.x = x
self.y = y
def move(self, dx: float, dy: float):
"""移动点的位置"""
self.x += dx
self.y += dy
# 子类:圆
class Circle(Point):
"""圆形,继承自Point"""
def __init__(self, x: float, y: float, radius: float):
super().__init__(x, y) # 复用父类初始化
self.radius = radius
def area(self) -> float:
"""计算圆面积"""
from math import pi
return pi * (self.radius ** 2)
# 使用示例
c = Circle(0, 0, 2)
print(c.x, c.y, c.radius) # 0 0 2
print(round(c.area(), 2)) # 12.57
# 类型关系检查
print(isinstance(c, Circle)) # True
print(isinstance(c, Point)) # True (子类实例也是父类实例)
print(issubclass(Circle, Point))# True
print(issubclass(Point, Circle))# False
要点:
- 子类通过 super() 调用父类构造函数,避免重复代码
- 子类拥有父类的属性/方法,并可新增专有能力(如 area)
- isinstance 检查对象是否为类或其子类的实例;issubclass 检查类之间的继承关系
讨论与反馈
欢迎在下方留言讨论,分享你的学习心得或提出问题。评论基于GitHub Issues,需要GitHub账号。