Python方法的重载和方法的覆盖

📅 发布时间:2026/10/11 11:00:52
Python方法的重载和方法的覆盖
前言先把一个前提讲清楚这个标题把重载和覆盖并列容易让人以为 Python 同时支持两者。实际上 Python 只支持方法覆盖overriding不支持传统意义上的方法重载overloading。方法覆盖子类定义与父类同名的方法运行时调用的是子类的版本。这个 Python 完全支持。方法重载在同一个作用域里用同一个名字、不同的参数列表定义多个函数/方法由编译器或运行环境根据实参个数或类型自动选择。Java、C 支持它Python 不支持——在 Python 里同一个名字第二次定义会把第一次的直接覆盖掉前一个定义连影子都不剩。需要区分的是Python 虽然有typing.overload这个名字相似的装饰器但它只服务于静态类型检查器运行时不做任何分派——文档明确写着被overload装饰的定义会被后面那个不带overload的定义覆盖运行时直接调overload装饰的函数会抛NotImplementedError。所以本文讲三件事覆盖怎么写为什么重载在 Python 里行不通以及当真的需要按参数个数或类型分派时用什么方式替代。一、方法覆盖Python 支持的写法覆盖就是子类重写父类方法。它依赖属性查找先实例、再沿 MRO 找类子类在 MRO 里排在父类前面所以子类版本被选中。# 适用于 Python 3.8class Base:def greet(self):return baseclass Child(Base):def greet(self): # 与父类同名 → 覆盖return childprint(Child().greet()) # child要在覆盖的同时保留父类的行为用super()调一下# 适用于 Python 3.8class Base:def greet(self):return baseclass Child(Base):def greet(self):return super().greet() childprint(Child().greet()) # basechild注意覆盖时保持调用签名兼容。子类如果偷偷改掉了参数个数多态调用点就会在某处突然抛TypeError。二、为什么重载在 Python 里行不通看下面这段看起来像重载的代码# 适用于 Python 3.8class Calculator:def add(self, a, b):return a bdef add(self, a, b, c): # 这个名字把上面那个覆盖掉了return a b ccalc Calculator()print(calc.add(1, 2, 3)) # 6# calc.add(1, 2) # TypeError: add() missing 1 required positional argument类体也是一个作用域两个def add同名后一个直接覆盖前一个。所以calc.add(1, 2)会失败——不是没匹配到两个参数的重载版本而是那个版本根本不存在。这背后的原因是Python 是动态类型语言函数对象只认名字不像静态编译语言那样在编译期根据签名生成多份符号。变量名在模块/类命名空间里就是唯一的键。因此Python 里想说支持多种调用形式只能靠一个函数体内部判断或者靠运行时的类型分派。三、替代方案一默认参数与可变参数如果不同重载版本的差异只在于参数个数用默认参数default argument或可变参数*args/**kwargs就够了。# 适用于 Python 3.8class Calculator:def add(self, a, b, c0):return a b ccalc Calculator()print(calc.add(1, 2)) # 3print(calc.add(1, 2, 3)) # 6如果差异在于类型朴素做法是在函数体里分支但那正是多态想消灭的写法# 适用于 Python 3.8def render(value):if isinstance(value, str):return str: valueif isinstance(value, int):return fint:{value}return str(value)这种写法勉强可用但每当新增类型就要回来改这个函数违反了开闭原则。四、替代方案二单分派singledispatch标准库functools提供了单分派泛型函数single-dispatch generic function把函数变成按第一个参数的类型选择实现的入口。# 适用于 Python 3.8from functools import singledispatchsingledispatchdef describe(value):return funknown:{value}describe.registerdef _(value: int):return fint:{value}describe.registerdef _(value: str):return fstr:{value}print(describe(10)) # int:10print(describe(hi)) # str:hiprint(describe(1.5)) # unknown:1.5要点singledispatch从 Python 3.4 起提供只按第一个参数的类型分派用describe.register配上类型注解来注册实现被singledispatch装饰的那个原函数注册在object上作为兜底。用于类方法时用functools.singledispatchmethod需要Python 3.8 及以上。此时分派依据是第一个非self/ 非cls的参数# 适用于 Python 3.8from functools import singledispatchmethodclass Formatter:singledispatchmethoddef format(self, value):return str(value)format.registerdef _(self, value: int):return fint:{value}format.registerdef _(self, value: str):return fstr:{value}f Formatter()print(f.format(3)) # int:3print(f.format(a)) # str:aprint(f.format(1.5)) # 1.5文档特别提醒singledispatchmethod 要和别的装饰器如classmethod叠加时必须放在最外层否则register拿不到正确的绑定。分派是沿类型的 MRO 找最近的注册实现也会考虑注册在抽象基类上的实现——所以bool是int的子类format(True)会走到int的实现输出int:True。下表对比三个容易混淆的概念概念运行时是否可依据参数个数/类型选择Python 支持情况方法覆盖 overriding否只看 MRO 选同名方法支持传统方法重载 overloading是不支持同名后者覆盖前者typing.overload否仅供类型检查器提供但运行时不分派singledispatch按第一个参数类型3.4 提供singledispatchmethod按第一个非 self 参数类型3.8 提供常见坑点1. 以为同名不同参数的方法算重载❌# 适用于 Python 3.8class C:def f(self, a): ...def f(self, a, b): ...✅ 只有第二个存在要支持两种调用写成def f(self, a, bNone):。2. 用typing.overload指望运行时按类型选❌ 以为加了overload就能运行时分派——它只是给类型检查器的声明运行时会抛NotImplementedError。✅ 运行时按类型分派用functools.singledispatch/singledispatchmethod。3.singledispatchmethod没放在最外层❌# 适用于 Python 3.8class C:classmethodsingledispatchmethoddef f(cls, x): ...✅ 让singledispatchmethod处于最外层register才能正确工作。4. 指望singledispatch按多个参数分派❌ 以为它能像多重分派multiple dispatch那样同时看两个参数。✅ 它只看第一个方法里是第一个非self参数的类型。5. 覆盖父类方法时改了签名❌ 父类def save(self, path)子类写成def save(self)多态调用点传入参数就TypeError。✅ 覆盖方法保持兼容签名新增能力用带默认值的参数。6. 覆盖后忘记保留父类行为❌ 子类重写__init__却不调super().__init__()父类设置的属性全丢。✅ 需要父类逻辑时用super().方法名(...)显式调用。7. 在函数体里堆isinstance分支冒充重载❌ 每加一种类型都要回来改函数还容易漏掉子类。✅ 简单场景用singledispatch面向对象的场景用方法覆盖。8. 注册实现时忘了类型注解❌describe.register下面的函数没写value: int注册不进int永远走兜底实现。✅ 注册函数必须带类型注解注解即分派依据。总结需求推荐做法子类改变父类行为方法覆盖必要时super()保留父类逻辑参数个数不同默认参数、*args/**kwargs按参数类型分派函数functools.singledispatch3.4按参数类型分派方法functools.singledispatchmethod3.8只想让类型检查器知道多种签名typing.overload运行时无效记住一句话就能避开绝大多数困惑Python 有覆盖没有传统重载。同名方法会互相覆盖需要多种形态时要么把差异交给继承与多态要么用singledispatch系列做运行时类型分派typing.overload则是纯给静态检查器看的另一回事。