6、继承的精髓:单继承与多继承,子类如何调用父类方法(`super()`),方法重写(override)的规则。

继承这东西,说白了就是「儿子用老子的东西,还能改一改」。我刚开始学面向对象时,觉得继承不就是复制粘贴吗?后来踩了坑才明白——继承的精髓在于「差异化复用」。你不需要重写所有东西,只需要把不一样的部分覆盖掉。

这一章,我们就把单继承、多继承、super() 调用、方法重写这几个核心概念彻底讲透。嗯,都是我在项目中反复用到的,你想想看,没有继承,代码得写成什么样?

6.1 单继承:最简单的父子关系

单继承就是一个子类只继承一个父类。这是最常用、也最不容易出错的继承方式。

核心要点:子类自动拥有父类的所有属性和方法(私有成员除外)。

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return f"{self.name} 发出声音"

class Dog(Animal):
    def wag_tail(self):
        return f"{self.name} 摇尾巴"

dog = Dog("旺财")
print(dog.speak())      # 继承自父类
print(dog.wag_tail())   # 子类自己的方法

你看,Dog 类只写了 wag_tail 方法,speak 方法是从 Animal 继承来的。这就是复用的力量。我在项目中经常用这种模式——先写一个通用的基类,然后派生出各种具体类,省掉大量重复代码。

6.2 方法重写(Override):子类说了算

子类如果觉得父类的方法不好用,可以自己重新实现一遍。这就是方法重写。

规则其实很简单:子类中定义和父类同名的方法,就会覆盖父类的版本。

class Animal:
    def speak(self):
        return "动物发出声音"

class Cat(Animal):
    def speak(self):          # 重写父类方法
        return "喵喵喵"

class Dog(Animal):
    def speak(self):          # 重写父类方法
        return "汪汪汪"

animals = [Animal(), Cat(), Dog()]
for a in animals:
    print(a.speak())
# 输出:
# 动物发出声音
# 喵喵喵
# 汪汪汪

为什么会这样?因为 Python 在调用方法时,会先在子类中找,找不到才去父类找。这就是「最近优先」原则。

我的习惯:重写方法时,尽量保持方法签名一致(参数个数、返回值类型)。虽然 Python 不强制,但保持一致能让代码更可预测。

6.3 super():子类调用父类方法

有时候,子类不想完全重写父类方法,而是想在父类方法的基础上加点东西。这时候就需要 super() 了。

super() 的作用就是「调用父类的版本」。我经常用它来扩展父类的 __init__ 方法。

class Vehicle:
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model
        print(f"Vehicle __init__: {brand} {model}")

    def start(self):
        print("车辆启动")

class Car(Vehicle):
    def __init__(self, brand, model, doors):
        super().__init__(brand, model)   # 调用父类的 __init__
        self.doors = doors
        print(f"Car __init__: {doors} 门")

    def start(self):
        super().start()                  # 调用父类的 start
        print("汽车启动完成")

car = Car("丰田", "凯美瑞", 4)
car.start()

输出结果:

Vehicle __init__: 丰田 凯美瑞
Car __init__: 4 门
车辆启动
汽车启动完成

你看,super() 让我们既能复用父类的逻辑,又能添加子类特有的逻辑。我曾经在项目中见过有人手动调用父类方法(比如 Vehicle.__init__(self, ...)),这样做虽然也能工作,但一旦涉及多继承就会出问题。所以,请用 super(),别偷懒。

避坑指南:我曾经在重构一个大型项目时,发现有人用「类名.方法名(self)」的方式调用父类方法。结果后来引入多继承,代码直接炸了。super() 不是语法糖,它处理了 MRO(方法解析顺序),手动调用会破坏这个机制。

6.4 多继承:Python 的「双刃剑」

Python 支持多继承,也就是一个子类可以有多个父类。这听起来很强大,但用不好就是灾难。

class Flyable:
    def fly(self):
        return "我能飞"

class Swimmable:
    def swim(self):
        return "我能游泳"

class Duck(Flyable, Swimmable):
    pass

duck = Duck()
print(duck.fly())    # 我能飞
print(duck.swim())   # 我能游泳

看起来挺美好对吧?但问题来了——如果多个父类有同名方法怎么办?

class A:
    def do(self):
        print("A.do")

class B:
    def do(self):
        print("B.do")

class C(A, B):
    pass

c = C()
c.do()   # 输出什么?

答案是 A.do。为什么?因为 Python 使用 C3 线性化算法来决定方法解析顺序(MRO)。你可以用 类名.__mro__ 查看顺序。

print(C.__mro__)
# (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

MRO 的顺序就是:C → A → B → object。所以调用 do() 时,先找 C,没有;再找 A,找到了,就用 A 的版本。

核心原则:多继承中,同名方法的调用顺序由 MRO 决定,而不是你写父类的顺序那么简单。MRO 遵循「深度优先,从左到右」的规则,但实际算法更复杂。

6.5 多继承中的 super():钻石问题

多继承最经典的坑就是「钻石问题」(也叫菱形继承)。

class Base:
    def __init__(self):
        print("Base.__init__")

class A(Base):
    def __init__(self):
        super().__init__()
        print("A.__init__")

class B(Base):
    def __init__(self):
        super().__init__()
        print("B.__init__")

class C(A, B):
    def __init__(self):
        super().__init__()
        print("C.__init__")

c = C()

输出结果:

Base.__init__
B.__init__
A.__init__
C.__init__

你可能会奇怪:为什么 Base.__init__ 只调用了一次?这就是 super() 配合 MRO 的威力。super() 不是简单地调用「父类」,而是按照 MRO 顺序调用「下一个类」。所以 C 的 MRO 是 C → A → B → Base → object,super() 会沿着这个链依次调用。

我的建议:多继承尽量少用。如果非用不可,确保所有父类都正确使用了 super(),并且理解 MRO 的顺序。我在项目中更倾向于用「组合」代替多继承——把功能拆成独立的类,然后用实例变量组合起来,这样更清晰。

6.6 方法重写的规则总结

规则 说明 示例
同名覆盖 子类定义与父类同名的方法,自动覆盖父类版本 子类 speak() 覆盖父类 speak()
签名一致 虽然 Python 不强制,但建议保持参数一致 父类 def foo(a, b),子类也 def foo(a, b)
访问父类 使用 super() 调用父类被覆盖的方法 super().foo()
私有方法 以双下划线开头的私有方法不会被继承 __private_method() 子类无法访问
静态方法/类方法 同样可以被重写,但需使用 @staticmethod/@classmethod 子类重写父类的 @staticmethod

6.7 知识体系图:继承的核心逻辑

继承核心知识体系 继 承 单继承 一个子类一个父类 自动继承所有非私有成员 多继承 多个父类 MRO决定调用顺序 方法重写 (Override) 同名方法覆盖父类 super()调用父类版本 核心原则:子类差异化复用父类,super()保证MRO正确 多继承慎用,优先考虑组合 单继承:简单可靠 多继承:灵活但复杂 重写:差异化定制

6.8 实战建议

说了这么多理论,最后给点实际建议:

  • 优先用单继承——90% 的场景单继承就够了,别为了炫技用多继承。
  • super() 是标配——不管单继承还是多继承,都用 super() 调用父类方法,别手动写类名。
  • 重写时保持签名一致——虽然 Python 不强制,但保持参数一致能让代码更可维护。
  • 多继承用组合替代——如果发现需要多继承,先想想能不能用「有一个」代替「是一个」。

最后说一句:继承不是万能的。我见过太多人为了用继承而用继承,结果搞出一堆复杂的继承链。记住,继承的目的是「复用和差异化」,不是「炫技」。能用组合解决的问题,就别硬上继承。


公众号:蓝海资料掘金营,微信deep3321