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() 的好处是:

  1. 避免直接引用父类,使代码更灵活
  2. 自动处理方法解析顺序(MRO)
  3. 支持协作式多继承调用
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 类通常:

  1. 提供某个特定方面的功能
  2. 不需要自己的构造函数(或构造函数很简单)
  3. 设计为与其他类组合使用
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}")

总结

继承是面向对象编程的核心概念之一:

  1. 继承基础:子类继承父类的属性和方法
  2. super() 函数:调用父类的方法,处理 MRO
  3. 方法重写:子类可以重写父类的方法来改变行为
  4. isinstance 和 issubclass:检查对象和类的继承关系
  5. 多继承:支持一个类继承多个父类,但需要注意 MRO
  6. Mixin 模式:通过多继承添加可选功能
  7. 抽象基类:使用 @abstractmethod 定义接口规范

继承使得代码复用成为可能,同时支持多态(不同子类可以以相同的方式使用)。在下一节中,我们将继续学习多重继承和方法解析顺序(MRO)的深入内容。