继承与多态

继承是面向对象的三大支柱之一(封装、继承、多态)。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) # 调用父类的 __init__
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()) # 也能跑!Robot 根本没继承 Animal

跟 Java 严格的接口不同,Python 是”行为驱动”的。

五、isinstanceissubclass

判断继承关系:

1
2
3
isinstance(d, Dog)       # True
isinstance(d, Animal) # True,子类也算父类
issubclass(Dog, Animal) # True

推荐:接口检查用 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() # B 还是 C?

Python 用 MRO(Method Resolution Order) 决定:

1
2
print(D.__mro__)
# (D, B, C, A, object)

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
# B
# C
# A
  • 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__ # (Foo, object)
isinstance(Foo(), object) # True

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
# shapes.py
"""图形类的继承体系。"""

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 # True,B 没重写
C.hi is A.hi # False,C 重写了

十三、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。

十四、常见陷阱

  1. 忘了 super().__init__():父类初始化没跑,属性缺失。
  2. 不理解 MRO 就用多重继承:某个方法调用了”意想不到”的父类版本。
  3. 过度继承:3+ 层的继承树几乎必然难维护。
  4. type(x) == Foo 遗漏子类:用 isinstance
  5. 重写父类方法签名不兼容:违反 LSP(里氏替换原则)。
  6. 在 Mixin 里访问父类未有的属性self.name 可能不存在。

十五、小结与延伸阅读

  • 继承用 class Sub(Base):
  • super().method(...) 调父类;
  • 多态 = 不同类实现同名方法,调用方无感;
  • Python 是鸭子类型,行为比类型更重要;
  • 多重继承用 MRO 决定查找顺序;
  • 组合优于继承;
  • Mixin 是提供片段功能的类;
  • 想强制子类实现方法用 abc。

延伸阅读:

下一篇 特殊方法(魔术方法)详解 我们讲让类”表现得像内置类型”的双下划线方法。