Day 25 - 继承与 super 函数
什么是继承?
继承(Inheritance)是面向对象编程中最核心的概念之一,它允许我们定义一个类(子类)作为另一个类(父类或基类)的扩展。子类继承了父类的属性和方法,同时可以添加自己特有的属性和方法,或者重写(override)父类的方法来改变行为。
继承的主要好处是代码复用:公共的属性和方法只需要在父类中定义一次,子类自动拥有这些特性。此外,继承还体现了"是一个"(is-a)的关系,例如:狗是一种动物,学生是一个人,苹果是一种水果。
在 Python 中,使用圆括号 () 在类名后指定父类来实现继承。
继承的基本语法
class 父类:
# 父类的定义
class 子类(父类):
# 子类的定义
class Animal:
"""动物基类"""
def __init__(self, name, age):
self.name = name
self.age = age
def speak(self):
raise NotImplementedError("子类必须实现 speak 方法")
def info(self):
print(f"名字:{self.name},年龄:{self.age}")
class Dog(Animal):
"""狗类,继承自动物类"""
def speak(self):
print(f"{self.name} 在叫:汪汪汪!")
class Cat(Animal):
"""猫类,继承自动物类"""
def speak(self):
print(f"{self.name} 在叫:喵喵喵!")
# 创建子类对象
dog = Dog("旺财", 3)
cat = Cat("小白", 2)
dog.info() # 名字:旺财,年龄:3
dog.speak() # 旺财 在叫:汪汪汪!
cat.info() # 名字:小白,年龄:2
cat.speak() # 小白 在叫:喵喵喵!
单继承与多继承
Python 支持两种继承方式:单继承(一个类只继承一个父类)和多继承(一个类继承多个父类)。虽然多继承功能强大,但容易导致代码复杂度增加和菱形继承问题,因此在使用时需要特别小心。
# 单继承示例
class Vehicle:
def __init__(self, brand, speed):
self.brand = brand
self.speed = speed
def display(self):
print(f"品牌:{self.brand},速度:{self.speed}km/h")
class Car(Vehicle):
"""汽车类,单继承自 Vehicle"""
def __init__(self, brand, speed, doors):
super().__init__(brand, speed) # 调用父类的 __init__
self.doors = doors
def display(self):
super().display() # 调用父类的方法
print(f"车门数:{self.doors}")
car = Car("丰田", 180, 4)
car.display()
# 品牌:丰田,速度:180km/h
# 车门数:4
super() 函数详解
super() 函数是 Python 中调用父类方法的标准方式。它返回一个代理对象,将方法调用转发给父类。使用 super() 的好处是:
- 避免直接引用父类,使代码更灵活
- 自动处理方法解析顺序(MRO)
- 支持协作式多继承调用
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
print(f"Person.__init__ 被调用:{name}, {age}")
def greet(self):
print(f"你好,我叫 {self.name}")
class Student(Person):
def __init__(self, name, age, student_id):
super().__init__(name, age) # 调用父类的 __init__
self.student_id = student_id
print(f"Student.__init__ 被调用:{student_id}")
def greet(self):
super().greet() # 调用父类的 greet
print(f"我的学号是 {self.student_id}")
student = Student("张三", 20, "2023001")
# Person.__init__ 被调用:张三, 20
# Student.__init__ 被调用:2023001
student.greet()
# 你好,我叫 张三
# 我的学号是 2023001
在子类中扩展父类的方法
子类可以在继承父类方法的基础上进行扩展或修改。常见的模式是:在子类的重写方法中先调用父类的方法,然后添加子类特有的逻辑。
class Shape:
def __init__(self, color="黑色"):
self.color = color
def area(self):
"""计算面积,子类应重写此方法"""
return 0
def describe(self):
print(f"这是一个{self.color}的形状")
class Rectangle(Shape):
def __init__(self, width, height, color="蓝色"):
super().__init__(color) # 调用父类构造器
self.width = width
self.height = height
def area(self):
"""重写父类的 area 方法"""
return self.width * self.height
def describe(self):
"""扩展父类的 describe 方法"""
super().describe() # 先调用父类的方法
print(f"宽:{self.width},高:{self.height},面积:{self.area()}")
class Circle(Shape):
def __init__(self, radius, color="红色"):
super().__init__(color)
self.radius = radius
def area(self):
"""重写父类的 area 方法"""
import math
return math.pi * self.radius ** 2
def describe(self):
"""扩展父类的 describe 方法"""
super().describe()
print(f"半径:{self.radius},面积:{self.area():.2f}")
rect = Rectangle(10, 5, "绿色")
rect.describe()
# 这是一个绿色的形状
# 宽:10,高:5,面积:50
circle = Circle(5)
circle.describe()
# 这是一个红色的形状
# 半径:5,面积:78.54
issubclass() 和 isinstance()
Python 提供了两个内置函数来检查类的继承关系:
issubclass(cls, classinfo):检查 cls 是否是 classinfo 的子类isinstance(obj, classinfo):检查 obj 是否是 classinfo 的实例
class Animal:
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
class Labrador(Dog):
pass
# issubclass 检查
print(issubclass(Dog, Animal)) # True:Dog 是 Animal 的子类
print(issubclass(Labrador, Dog)) # True:Labrador 是 Dog 的子类
print(issubclass(Labrador, Animal)) # True:Labrador 也是 Animal 的子类(传递性)
print(issubclass(Dog, (Dog, Cat))) # True:Dog 是 (Dog, Cat) 元组中任意一个的子类
# isinstance 检查
dog = Dog()
cat = Cat()
lab = Labrador()
print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True:Dog 实例也是 Animal 实例
print(isinstance(lab, Labrador)) # True
print(isinstance(lab, Dog)) # True
print(isinstance(lab, (Dog, Cat))) # True
# 常见错误
print(isinstance(dog, Labrador)) # False:Dog 实例不是 Labrador
继承与属性访问
当访问对象的属性时,Python 会按照特定的顺序在继承链中查找,这个顺序叫做"方法解析顺序"(Method Resolution Order, MRO)。对于单继承,MRO 是从子类到父类,再到父类的父类,直到 object 类。
class A:
x = 1
class B(A):
pass
class C(A):
x = 2
class D(B, C):
pass
print(D.x) # 1(继承自 A)
print(D.__mro__) # 查看完整的 MRO
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
# 修改 C 的 x
C.x = 3
print(D.x) # 1(仍然是 1,因为 D 直接继承 B,B 继承 A)
父类方法的可访问性
子类可以访问父类的公共(public)和受保护(protected)成员,但不能直接访问父类的私有成员。不过,私有成员可以通过父类提供的公共方法来访问。
class Base:
def __init__(self):
self.public = "公开"
self._protected = "受保护"
self.__private = "私有" # 名称改写为 _Base__private
def public_method(self):
return "公共方法"
def _protected_method(self):
return "受保护方法"
def __private_method(self):
return "私有方法"
def access_private(self):
"""通过公共方法访问私有成员"""
return self.__private
class Derived(Base):
def access_base(self):
print(self.public) # OK
print(self._protected) # OK(约定可以访问)
# print(self.__private) # 错误:AttributeError
print(self._Base__private) # 技术上可行,但不推荐
print(self.access_private()) # OK:通过公共方法访问
多继承中的 super()
在多继承情况下,super() 的行为会更加复杂。它遵循 C3 线性化算法来确定方法解析顺序。关键点是:super() 不一定调用直接父类的方法,而是调用 MRO 中的下一个类。
class A:
def __init__(self):
print("A.__init__")
super().__init__()
class B:
def __init__(self):
print("B.__init__")
super().__init__()
class C(A, B):
def __init__(self):
print("C.__init__")
super().__init__()
# 注意调用顺序
print("创建 C 对象:")
c = C()
# 输出:
# C.__init__
# A.__init__
# B.__init__
print("\nC 的 MRO:")
for cls in C.__mro__:
print(f" {cls.__name__}")
Mixin 模式
Mixin(混入)是一种设计模式,用于在多继承中向类添加可选功能。Mixin 类通常:
- 提供某个特定方面的功能
- 不需要自己的构造函数(或构造函数很简单)
- 设计为与其他类组合使用
class FlyMixin:
"""飞行能力混入"""
def fly(self):
print(f"{self.name} 正在飞翔")
class SwinMixin:
"""游泳能力混入"""
def swim(self):
print(f"{self.name} 正在游泳")
class WalkMixin:
"""行走能力混入"""
def walk(self):
print(f"{self.name} 正在行走")
class Animal:
def __init__(self, name):
self.name = name
class Duck(Animal, FlyMixin, SwinMixin, WalkMixin):
"""鸭子:会飞、会游泳、会走"""
pass
class Dog(Animal, WalkMixin, SwinMixin):
"""狗:会走、会游泳"""
pass
class Penguin(Animal, SwinMixin, WalkMixin):
"""企鹅:会游泳、会走(但不会飞)"""
pass
duck = Duck("唐老鸭")
duck.fly() # 唐老鸭 正在飞翔
duck.swim() # 唐老鸭 正在游泳
duck.walk() # 唐老鸭 正在行走
dog = Dog("旺财")
# dog.fly() # AttributeError
dog.swim() # 旺财 正在游泳
dog.walk() # 旺财 正在行走
抽象基类(ABC)
抽象基类(Abstract Base Class)用于定义接口规范,强制子类实现某些方法。Python 的 abc 模块提供了 ABC 类和 @abstractmethod 装饰器来实现抽象基类。
from abc import ABC, abstractmethod
class Shape(ABC):
"""抽象基类:形状"""
def __init__(self, color="黑色"):
self.color = color
@abstractmethod
def area(self):
"""抽象方法:计算面积,子类必须实现"""
pass
@abstractmethod
def perimeter(self):
"""抽象方法:计算周长,子类必须实现"""
pass
def describe(self):
"""普通方法:描述形状"""
print(f"这是一个{self.color}的{self.__class__.__name__}")
class Rectangle(Shape):
def __init__(self, width, height, color="蓝色"):
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)
# 不能直接实例化抽象基类
# shape = Shape() # TypeError
rect = Rectangle(10, 5)
print(f"面积:{rect.area()}") # 50
print(f"周长:{rect.perimeter()}") # 30
rect.describe() # 这是一个蓝色的Rectangle
练习题
练习 1:员工管理系统
from abc import ABC, abstractmethod
class Employee(ABC):
"""员工抽象基类"""
def __init__(self, emp_id, name):
self.emp_id = emp_id
self.name = name
@abstractmethod
def calculate_salary(self):
"""计算月薪(抽象方法)"""
pass
def display(self):
"""显示员工信息"""
print(f"工号:{self.emp_id},姓名:{self.name},月薪:{self.calculate_salary():.2f}")
class FullTimeEmployee(Employee):
"""全职员工"""
def __init__(self, emp_id, name, base_salary):
super().__init__(emp_id, name)
self.base_salary = base_salary
def calculate_salary(self):
return self.base_salary
class PartTimeEmployee(Employee):
"""兼职员工"""
def __init__(self, emp_id, name, hours, hourly_rate):
super().__init__(emp_id, name)
self.hours = hours
self.hourly_rate = hourly_rate
def calculate_salary(self):
return self.hours * self.hourly_rate
class SalesEmployee(Employee):
"""销售员工"""
def __init__(self, emp_id, name, base_salary, commission, sales_amount):
super().__init__(emp_id, name)
self.base_salary = base_salary
self.commission = commission
self.sales_amount = sales_amount
def calculate_salary(self):
return self.base_salary + self.commission * self.sales_amount
# 测试
employees = [
FullTimeEmployee("E001", "张三", 8000),
PartTimeEmployee("E002", "李四", 80, 50),
SalesEmployee("E003", "王五", 5000, 0.1, 50000)
]
for emp in employees:
emp.display()
# 工号:E001,姓名:张三,月薪:8000.00
# 工号:E002,姓名:李四,月薪:4000.00
# 工号:E003,姓名:王五,月薪:10000.00
练习 2:几何图形层次结构
from abc import ABC, abstractmethod
import math
class Shape(ABC):
def __init__(self, color="黑色"):
self.color = color
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
def __str__(self):
return f"{self.__class__.__name__}(color={self.color})"
class Circle(Shape):
def __init__(self, radius, color="红色"):
super().__init__(color)
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
def perimeter(self):
return 2 * math.pi * self.radius
class Triangle(Shape):
def __init__(self, a, b, c, color="蓝色"):
super().__init__(color)
self.a = a
self.b = b
self.c = c
def area(self):
# 海伦公式
s = (self.a + self.b + self.c) / 2
return math.sqrt(s * (s - self.a) * (s - self.b) * (s - self.c))
def perimeter(self):
return self.a + self.b + self.c
class Rectangle(Shape):
def __init__(self, width, height, color="绿色"):
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)
# 测试
shapes = [
Circle(5, "红色"),
Triangle(3, 4, 5, "蓝色"),
Rectangle(10, 5, "绿色")
]
for shape in shapes:
print(f"{shape}")
print(f" 面积:{shape.area():.2f}")
print(f" 周长:{shape.perimeter():.2f}")
总结
继承是面向对象编程的核心概念之一:
- 继承基础:子类继承父类的属性和方法
- super() 函数:调用父类的方法,处理 MRO
- 方法重写:子类可以重写父类的方法来改变行为
- isinstance 和 issubclass:检查对象和类的继承关系
- 多继承:支持一个类继承多个父类,但需要注意 MRO
- Mixin 模式:通过多继承添加可选功能
- 抽象基类:使用
@abstractmethod定义接口规范
继承使得代码复用成为可能,同时支持多态(不同子类可以以相同的方式使用)。在下一节中,我们将继续学习多重继承和方法解析顺序(MRO)的深入内容。