继承是面向对象的三大支柱之一(封装、继承、多态)。Python 的继承机制既灵活又危险——它支持”多重继承”,能让你写出比 Java 更简洁的代码,但也可能让你陷入”钻石继承”的迷宫。这一篇讲清楚:怎么继承、super() 是什么、MRO 方法解析顺序、多态的本质、以及什么时候不该用继承(组合优于继承)。
一、继承的基本语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Animal: def __init__(self, name): self.name = name
def eat(self): print(f"{self.name} 在吃")
class Dog(Animal): def bark(self): print(f"{self.name} 在叫")
d = Dog("旺财") d.eat() d.bark()
|
class Dog(Animal):Animal 是父类(基类),Dog 是子类;
- 子类继承父类的所有属性和方法;
- 子类可以新增自己的方法;
- 子类可以重写父类的方法。
二、super():调用父类方法
想在子类里”扩展”(而不是完全替换)父类的方法:
1 2 3 4 5 6 7 8 9 10 11 12
| class Animal: def __init__(self, name): self.name = name
class Dog(Animal): def __init__(self, name, breed): super().__init__(name) self.breed = breed
d = Dog("旺财", "拉布拉多") print(d.name, d.breed)
|
super() 是 Python 里几乎所有子类都要用的东西。特别是 __init__ 里,忘了调用父类通常会漏掉某些初始化。
三、方法重写(override)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Animal: def speak(self): print("动物在叫")
class Dog(Animal): def speak(self): print("汪汪")
class Cat(Animal): def speak(self): print("喵喵")
Dog().speak() Cat().speak()
|
这就是多态——同一个方法名 speak(),不同的类有不同的实现。
四、多态:以行为为中心
多态的本质:调用方不用关心具体类型,只要那个对象有对应方法就行。
1 2 3 4 5
| def make_sound(animal): animal.speak()
make_sound(Dog()) make_sound(Cat())
|
make_sound 不检查 isinstance(animal, Animal)——只要能 .speak() 就行。这个思想在 Python 里叫 duck typing(鸭子类型)——“看起来像鸭子,叫起来像鸭子,那就是鸭子”。
1 2 3 4 5
| class Robot: def speak(self): print("bzzt")
make_sound(Robot())
|
跟 Java 严格的接口不同,Python 是”行为驱动”的。
五、isinstance 与 issubclass
判断继承关系:
1 2 3
| isinstance(d, Dog) isinstance(d, Animal) issubclass(Dog, Animal)
|
推荐:接口检查用 isinstance。不用 type(x) == Dog,那样会漏掉子类。
六、多重继承
Python 支持一个类继承多个父类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Swimmer: def swim(self): print("在游泳")
class Runner: def run(self): print("在跑步")
class Triathlete(Swimmer, Runner): pass
t = Triathlete() t.swim() t.run()
|
优点:可以”组合”多种能力;
缺点:容易陷入”钻石继承”问题。
钻石继承与 MRO
1 2 3 4 5 6 7 8 9 10 11 12 13
| class A: def hi(self): print("A")
class B(A): def hi(self): print("B")
class C(A): def hi(self): print("C")
class D(B, C): pass
D().hi()
|
Python 用 MRO(Method Resolution Order) 决定:
Python 用 C3 线性化算法计算 MRO,规则:
- 子类在父类前;
- 多个父类按声明顺序;
- 保证每个类只出现一次。
D().hi() 会输出 B,因为 MRO 里 B 在 C 之前。
建议:写代码时用少而清晰的继承(一层继承就够 90% 场景)。真需要多重继承,画个 MRO 图确认一下。
七、super() 的正确用法
在多重继承里,super() 不是”调用父类”——它是”沿着 MRO 找下一个“。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class A: def m(self): print("A")
class B(A): def m(self): print("B") super().m()
class C(A): def m(self): print("C") super().m()
class D(B, C): def m(self): print("D") super().m()
D().m()
|
- D 调 super() → 找 MRO 下一个 → B;
- B 调 super() → C(不是 A!因为 MRO 是 D→B→C→A→object);
- C 调 super() → A。
这个特性叫”协作式多重继承”,能让每个类都被访问一次。要用好,每个类都要写 super().method(...)。
八、组合优于继承
继承的问题:
- 强耦合父类接口;
- 深层继承树难理解;
- “is-a” 关系有时不对(
Rectangle vs Square 是经典反例)。
替代方案:组合——把功能作为属性持有:
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Duck(Bird, Swimmer, Quacker): ...
class Duck: def __init__(self): self.beak = Beak() self.wings = Wings() self.legs = Legs()
def fly(self): self.wings.flap()
|
经验法则:只有 A 真的是 B(is-a 关系)时才继承。A 只是有 B(has-a)时用组合。
九、抽象方法(预告)
想强制子类实现某个方法,用 abc 模块:
1 2 3 4 5 6 7 8 9 10 11 12
| from abc import ABC, abstractmethod
class Shape(ABC): @abstractmethod def area(self): ...
class Circle(Shape): def area(self): return 3.14 * self.r ** 2
Shape()
|
完整讲解见 抽象类与接口。
十、Python 里所有类都继承 object
即使不写 (object),Python 3 里所有类默认继承 object:
1 2 3 4 5
| class Foo: pass
Foo.__mro__ isinstance(Foo(), object)
|
object 提供了 __init__、__repr__、__hash__ 等默认行为。
十一、一个完整例子:图形类层次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| """图形类的继承体系。"""
from abc import ABC, abstractmethod from math import pi
class Shape(ABC): """所有图形的抽象基类。"""
@abstractmethod def area(self) -> float: ...
@abstractmethod def perimeter(self) -> float: ...
def describe(self) -> None: print(f"{self.__class__.__name__}: 面积 {self.area():.2f}, " f"周长 {self.perimeter():.2f}")
class Circle(Shape): def __init__(self, r: float): self.r = r
def area(self): return pi * self.r ** 2
def perimeter(self): return 2 * pi * self.r
class Rectangle(Shape): def __init__(self, w: float, h: float): self.w, self.h = w, h
def area(self): return self.w * self.h
def perimeter(self): return 2 * (self.w + self.h)
class Square(Rectangle): def __init__(self, side: float): super().__init__(side, side)
shapes = [Circle(3), Rectangle(4, 5), Square(6)] for s in shapes: s.describe()
|
十二、检查方法是否被重写
1 2 3 4 5 6 7 8 9 10 11
| class A: def hi(self): print("A")
class B(A): pass
class C(A): def hi(self): print("C")
B.hi is A.hi C.hi is A.hi
|
十三、Mixin 模式
Mixin 是”提供小片功能的类”,通常不单独实例化,只用来被组合:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class JSONMixin: def to_json(self): import json return json.dumps(self.__dict__)
class LoggableMixin: def log(self, msg): print(f"[{self.__class__.__name__}] {msg}")
class User(JSONMixin, LoggableMixin): def __init__(self, name): self.name = name
u = User("咖飞") u.log("hello") print(u.to_json())
|
Django、SQLAlchemy 等框架大量使用 mixin。
十四、常见陷阱
- 忘了
super().__init__():父类初始化没跑,属性缺失。
- 不理解 MRO 就用多重继承:某个方法调用了”意想不到”的父类版本。
- 过度继承:3+ 层的继承树几乎必然难维护。
type(x) == Foo 遗漏子类:用 isinstance。
- 重写父类方法签名不兼容:违反 LSP(里氏替换原则)。
- 在 Mixin 里访问父类未有的属性:
self.name 可能不存在。
十五、小结与延伸阅读
- 继承用
class Sub(Base):;
super().method(...) 调父类;
- 多态 = 不同类实现同名方法,调用方无感;
- Python 是鸭子类型,行为比类型更重要;
- 多重继承用 MRO 决定查找顺序;
- 组合优于继承;
- Mixin 是提供片段功能的类;
- 想强制子类实现方法用 abc。
延伸阅读:
下一篇 特殊方法(魔术方法)详解 我们讲让类”表现得像内置类型”的双下划线方法。