Skip to content

Python 继承和多态:站在巨人的肩膀上编程

引言:家族基因的传承

想象一个家族:爷爷会种地,爸爸继承了种地的技能,又学会了开拖拉机;儿子继承了爷爷和爸爸的所有技能,还学会了无人机喷洒农药。

每一代人都不用从零开始——上一代的技能直接"遗传"下来,新一代只需学习新增的技能,或者改进旧技能(比如用无人机代替人工)。

Python 的**继承(Inheritance)**就是编程世界的"基因传承":子类自动获得父类的全部能力,还能新增自己的特色,或者改写父类不适合的做法

而**多态(Polymorphism)**则是:不管家族里哪一代人,只要叫他"干活",他都会用自己的方式去完成——爷爷用锄头,爸爸用拖拉机,儿子用无人机。


一、继承:子类自动获得父类的能力

1.1 定义父类

python
class Animal(object):
    def run(self):
        print('Animal is running...')

Animal 是父类(也叫基类、超类),它有一个 run() 方法。

1.2 定义子类:括号里写父类名

python
class Dog(Animal):
    pass

class Cat(Animal):
    pass

DogCat 什么都没写,只是括号里声明了"我继承 Animal"。但它们已经自动拥有了 run() 方法

python
dog = Dog()
dog.run()   # Animal is running...

cat = Cat()
cat.run()   # Animal is running...

生活化理解:爸爸会开拖拉机,儿子不用重新学,天生就会——这就是继承。

1.3 子类新增自己的方法

python
class Dog(Animal):
    def eat(self):
        print('Eating meat...')

现在 Dog 既有继承来的 run(),又有自己特有的 eat()

python
dog = Dog()
dog.run()   # Animal is running...(继承来的)
dog.eat()   # Eating meat...(自己特有的)

继承的核心价值代码复用。不用重复写公共逻辑,子类只需关注"我和父类有什么不同"。


二、方法重写:子类改写父类的做法

2.1 问题:所有动物都跑成一个样

上面的 Dog 和 Cat 调用 run(),输出的都是 Animal is running...——不够精确。狗跑和猫跑应该不一样。

2.2 子类覆盖父类方法

在子类中重新定义同名方法,就会覆盖父类的版本:

python
class Dog(Animal):
    def run(self):
        print('Dog is running...')

class Cat(Animal):
    def run(self):
        print('Cat is running...')

现在:

python
dog = Dog()
dog.run()   # Dog is running...

cat = Cat()
cat.run()   # Cat is running...

规则:调用方法时,Python 先找子类,找不到再找父类,还找不到就报错

生活化理解:爸爸用拖拉机耕地,儿子说"我不用拖拉机,我用无人机"——他重写了爸爸的方法。但儿子仍然"会耕地"这个技能,只是方式变了。


三、多态:同一接口,不同实现

3.1 先理解:子类实例也是父类类型

python
dog = Dog()

>>> isinstance(dog, Dog)
True

>>> isinstance(dog, Animal)
True

dog 既是 Dog 类型,也是 Animal 类型——因为 Dog 是 Animal 的一种

但反过来不行:

python
animal = Animal()

>>> isinstance(animal, Dog)
False

Animal 不是 Dog——"动物"不一定是"狗"。

生活化理解:儿子是家族成员,但家族成员不一定是儿子。

3.2 多态的威力:写一次,处处可用

定义一个函数,接收 Animal 类型:

python
def run_twice(animal):
    animal.run()
    animal.run()

传入任何 Animal 或其子类的实例,都能正常工作:

python
>>> run_twice(Animal())
Animal is running...
Animal is running...

>>> run_twice(Dog())
Dog is running...
Dog is running...

>>> run_twice(Cat())
Cat is running...
Cat is running...

3.3 最精彩的部分:新增子类,老代码不动

再定义一个 Tortoise(乌龟):

python
class Tortoise(Animal):
    def run(self):
        print('Tortoise is running slowly...')

直接传给 run_twice

python
>>> run_twice(Tortoise())
Tortoise is running slowly...
Tortoise is running slowly...

run_twice 一行代码没改,却支持了新类型!

3.4 这就是"开闭原则"

对扩展开放:可以随时新增 Animal 的子类; 对修改封闭:不需要修改依赖 Animal 的现有代码(如 run_twice)。

生活化理解:家族里新添了曾孙,他会开飞船。家族聚会时喊"大家去干活",曾孙自动用飞船,其他人用各自的方式——喊的人不用管具体是谁,只需要知道"他们都是家族成员,都会干活"


四、继承树:从 object 开始的家族谱系

所有类最终都继承自 object,形成一棵倒着的树:

                ┌───────────────┐
                │    object     │
                └───────┬───────┘

           ┌────────────┴────────────┐
           │                         │
           ▼                         ▼
    ┌─────────────┐           ┌─────────────┐
    │   Animal    │           │    Plant    │
    └──────┬──────┘           └──────┬──────┘
           │                         │
      ┌────┴────┐               ┌────┴────┐
      │         │               │         │
      ▼         ▼               ▼         ▼
  ┌──────┐  ┌──────┐      ┌──────┐  ┌──────┐
  │  Dog │  │  Cat │      │ Tree │  │Flower│
  └──────┘  └──────┘      └──────┘  └──────┘
  • Dog 的父类是 AnimalAnimal 的父类是 object
  • Dog 继承了 Animal 的方法,也继承了 object 的方法(如 __init____str__ 等);
  • 可以一级一级往下继承:爷爷 → 爸爸 → 儿子 → 孙子……

五、鸭子类型:Python 的灵活之道

5.1 静态语言 vs 动态语言

Java(静态语言):函数声明 run_twice(Animal animal),传入的对象必须是 Animal 或其子类,否则编译报错。

Python(动态语言):不强制要求继承关系,只要对象有 run() 方法就行

python
class Timer(object):
    def run(self):
        print('Start...')

run_twice(Timer())   # Start... Start...

Timer 和 Animal 毫无继承关系,但它有 run() 方法,所以能传给 run_twice

5.2 什么是鸭子类型?

"如果一个东西走起来像鸭子,叫起来像鸭子,那它就是鸭子。"

Python 不关心"你是什么类型",只关心"你能做什么"。

经典例子:file-like object

Python 中很多函数接收"文件对象"参数,但实际上任何有 read() 方法的对象都可以

python
# 真正的文件
with open('data.txt') as f:
    content = f.read()

# 内存中的"假文件"
from io import StringIO
fake_file = StringIO('Hello, world!')
content = fake_file.read()

StringIO 不是文件,但它有 read() 方法,所以能被当成文件用。

生活化理解:餐厅招"会切菜的",不管你是有厨师证(继承自 Chef 类)还是自学成才(鸭子类型),只要能把菜切好就录用。


六、常见误区与避坑指南

6.1 误区一:子类重写时忘记调用 super()

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

class Dog(Animal):
    def __init__(self, name, breed):
        self.breed = breed    # ❌ 忘了初始化 name!

dog = Dog('Buddy', 'Golden')
print(dog.name)   # AttributeError: 'Dog' object has no attribute 'name'

修复:用 super() 调用父类的 __init__

python
class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)   # ✅ 先让父类初始化 name
        self.breed = breed       # 再初始化自己的

6.2 误区二:过度使用继承

python
# ❌ 继承链太深:A → B → C → D → E
class A: pass
class B(A): pass
class C(B): pass
class D(C): pass
class E(D): pass

继承链超过 3 层就要警惕——代码可读性急剧下降,改顶层影响所有底层。

替代方案:用组合代替继承——把功能拆成小类,通过"包含"关系组装,而不是"继承"关系。

6.3 误区三:以为多态必须靠继承

python
# ❌ 为了用多态,强行让不相关的类继承同一个父类
class Flyable(object):
    def fly(self): pass

class Bird(Flyable): pass
class Airplane(Flyable): pass
class Superman(Flyable): pass

在 Python 中,鸭子类型就够了——只要它们都有 fly() 方法,不需要强行继承。

python
# ✅ 各自独立,但有共同的方法名
class Bird:
    def fly(self): print('Bird flies')

class Airplane:
    def fly(self): print('Airplane flies')

def let_it_fly(flyable):
    flyable.fly()

let_it_fly(Bird())       # Bird flies
let_it_fly(Airplane())   # Airplane flies

6.4 误区四:子类覆盖父类方法时改变了参数签名

python
class Animal(object):
    def run(self, speed):
        print(f'Running at {speed}')

class Dog(Animal):
    def run(self):           # ❌ 参数变了!
        print('Dog running')

def make_run(animal, speed):
    animal.run(speed)        # 传入 speed

dog = Dog()
make_run(dog, 10)            # TypeError: run() takes 1 positional argument but 2 were given

原则:子类重写方法时,参数列表应该和父类兼容(可以更多,不能更少,更不能不兼容)。


七、实战练习

练习 1:形状继承体系

定义一个 Shape 父类,包含方法 area()(返回 0)。然后定义 RectangleCircle 继承 Shape,各自实现正确的面积计算。

python
shapes = [Rectangle(3, 4), Circle(5)]
for s in shapes:
    print(s.area())
# 12
# 78.5
参考答案
python
class Shape(object):
    def area(self):
        return 0

class Rectangle(Shape):
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length * self.width

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius ** 2

练习 2:利用多态写通知系统

定义 Notifier 父类(有 send(msg) 方法),以及子类 EmailNotifierSmsNotifierWechatNotifier,各自实现不同的发送方式。写一个函数 notify_all(notifiers, msg) 给所有通知器发消息。

参考答案
python
class Notifier(object):
    def send(self, msg):
        pass

class EmailNotifier(Notifier):
    def send(self, msg):
        print(f'邮件发送:{msg}')

class SmsNotifier(Notifier):
    def send(self, msg):
        print(f'短信发送:{msg}')

class WechatNotifier(Notifier):
    def send(self, msg):
        print(f'微信发送:{msg}')

def notify_all(notifiers, msg):
    for n in notifiers:
        n.send(msg)

notify_all([EmailNotifier(), SmsNotifier(), WechatNotifier()], '系统维护通知')
# 邮件发送:系统维护通知
# 短信发送:系统维护通知
# 微信发送:系统维护通知

新增通知方式(如钉钉)只需加新类,notify_all 完全不用改——这就是开闭原则。

练习 3:找出 bug

下面代码运行会报错,找出原因并修复:

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

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

dog = Dog('Buddy', 'Golden')
print(dog.name)
参考答案

Dog.__init__ 没有调用 Animal.__init__,导致 name 属性没有被初始化。

修复

python
class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)   # 调用父类初始化
        self.breed = breed

八、小结

  1. 继承:子类自动获得父类的全部方法,代码复用的核心手段;
  2. 重写:子类可以覆盖父类方法,调用时优先使用子类版本;
  3. 多态:接收父类类型的函数,可以传入任何子类实例,自动调用实际类型的方法——新增子类不影响老代码
  4. 继承树:所有类最终追溯到 object,形成倒树结构;
  5. 鸭子类型:Python 的灵活之处——不强制继承,只要"有这个方法"就行;
  6. 避坑要点:重写时记得 super()、继承链别太深、Python 中多态不必须靠继承、子类方法签名要兼容父类。

继承让我们"站在巨人的肩膀上",多态让我们"写一次代码,支持无数扩展"。但记住:Python 的鸭子类型给了我们更多自由——继承是手段,不是目的