Skip to content

方法重写 ​

上一节学习 MRO 时,如果子类中没有需要的方法,Python 会继续按照搜索顺序到父类中查找。

但父类提供的通常是通用行为,具体子类可能需要不同的实现。动物都有 eat(),老虎和狮子吃东西时却可以有自己的表现。子类应该完全替换父类行为,还是保留父类逻辑后再扩展?

方法重写概念 ​

子类重新定义父类中的同名方法,叫作方法重写,也叫 override。

python
class 父类:
    def 方法名(self):
        pass


class 子类(父类):
    def 方法名(self):
        pass

子类对象调用这个方法时,会先找到子类自己的版本。父类原来的方法仍然保留,父类对象调用时不会受到影响。

方法重写通常分成两种情况:

  1. 完全使用子类自己的实现
  2. 保留父类逻辑,再增加子类自己的操作

父类方法定义 ​

先定义 Animal,提供所有动物都能使用的 eat():

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

    def eat(self):
        print(f'{self.name} 正在吃东西')


animal = Animal('普通动物', 1, 20)
animal.eat()

运行结果:

text
普通动物 正在吃东西

接下来分别用老虎和狮子演示两种重写方式。

完全覆盖父类方法 ​

如果父类的方法不适合当前子类,应该如何处理?可以在子类中编写完整的新实现:

python
class Tiger(Animal):
    # 子类重新定义同名方法,完全使用自己的实现
    def eat(self):
        print(f'{self.name} 正在撕咬食物')
        print(f'{self.name} 发出咔咔的声音')


tiger = Tiger('泰哥', 1.88, 360)
animal.eat()
tiger.eat()

运行结果:

text
普通动物 正在吃东西
泰哥 正在撕咬食物
泰哥 发出咔咔的声音

Tiger.eat() 没有调用父类方法,执行的全部是老虎自己的逻辑。这种写法可以称为完全覆盖。

同时调用 animal.eat() 可以看到,Animal.eat() 没有被修改。方法重写只改变当前子类对象优先使用哪个版本。

方法重写机制:属性查找顺序与命名空间遮蔽

基于父类逻辑扩展 ​

如果父类方法仍然有用,如何避免重复编写通用逻辑?可以先通过 super() 调用父类版本,再补充子类逻辑:

python
class Lion(Animal):
    def eat(self):
        # 先执行父类的通用逻辑
        super().eat()
        # 再补充子类自己的逻辑
        print(f'{self.name} 吃完后发出吼声')


lion = Lion('辛巴', 2, 380)
lion.eat()

运行结果:

text
辛巴 正在吃东西
辛巴 吃完后发出吼声

执行 lion.eat() 时,程序会按下面的顺序运行:

  1. 进入 Lion.eat()
  2. 执行 super().eat(),调用父类的通用逻辑
  3. 回到 Lion.eat(),继续执行子类新增的逻辑

这里的 super() 与初始化父类属性时使用的是同一个工具:它会沿着 MRO 继续查找指定方法。

super() 扩展父类逻辑:控制权交接与时序闭环

重写策略对比 ​

重写方式是否调用 super()适用情况
完全覆盖否父类逻辑不适合子类,需要全部替换
基于父类扩展是父类逻辑仍然有用,子类只增加部分操作

选择哪一种方式,取决于父类逻辑是否还需要保留。不要为了使用 super() 而强行调用父类方法,也不要在可以复用父类逻辑时重复编写相同代码。

方法重写策略决策树与双模式架构对比

案例:内置列表方法扩展 ​

需求:继承 list,给 append() 增加一条操作提示,同时保留原有的列表追加行为。

功能分析:在子类中重写 append(),先输出提示,再用 super() 调用内置列表的实现。

python
class LoggedList(list):
    def append(self, value):
        print(f'准备添加:{value}')
        super().append(value)


names = LoggedList(['张三', '李四'])
names.append('王五')
print(names)

运行结果:

text
准备添加:王五
['张三', '李四', '王五']

LoggedList.append() 先输出提示,再调用 list.append() 完成真正的添加操作。原方法负责的核心行为和返回规则都没有改变,只增加了日志功能。

这个示例先了解即可。继承内置类时,应尽量保持原有方法的使用习惯,避免让同名方法表现出完全不同的含义。

内置类方法扩展机制:切面拦截模型与三大避坑指南

super() 扩展的三大时序模式:前置拦截、后置增强与环绕包装

小结 ​

本节重点掌握三件事:

  1. 方法重写就是在子类中重新定义父类的同名方法
  2. 完全覆盖只执行子类逻辑,不会修改父类原来的方法
  3. 使用 super().方法名() 可以先复用父类逻辑,再增加子类操作

练习 ​

  1. 定义 Animal 和 Dog,让 Dog 完全覆盖父类的 eat()
  2. 修改 Dog.eat(),先调用父类方法,再输出小狗自己的进食动作
  3. 分别创建父类和子类对象,对比它们调用 eat() 的运行结果
  4. 继承 dict,重写 clear(),清空前先输出字典中原有的数据