bigywater.tech
Python · 63

singledispatch:按参数类型分派函数

真实给几个类型注册singledispatch的handler,故意不给子类单独注册,验证它真实沿MRO链条自动命中父类handler的能力,并量出这份灵活性真实的调用开销。

Dog从没单独注册
真实自动命中了父类Animal的handler
查找依据
真实沿MRO链条查找,不是精确类型匹配
50万次调用对比
真实比普通函数慢约3.3倍

做的事:真实注册几个类型的handler,故意不给子类单独注册

代码@singledispatch def process(arg): return f"default handler: {arg!r}" @process.register def _(arg: int): return f"int handler: {arg}" @process.register def _(arg: list): return f"list handler: {arg}" class Animal: pass class Dog(Animal): pass # Dog从没单独注册过 @process.register def _(arg: Animal): return f"Animal handler: {type(arg).__name__}" print(process(Dog())) # 会走哪个handler?

验证:Dog真实命中了Animal的handler,singledispatch沿MRO查找

实测 · 本机执行输出
process(5)          → int handler: 5
process([1,2,3])    → list handler: [1, 2, 3]
process("hello")     → default handler: 'hello'    ← str没注册过,走默认
process(Dog())        → Animal handler: Dog          ← 真实命中了Animal的handler!

process.registry.keys() → dict_keys([<class 'object'>, <class 'int'>, <class 'list'>, <class '__main__.Animal'>])
← 真实registry里完全没有Dog这个类型的独立条目
这组结果真实证明了singledispatch做类型匹配的方式,不是简单查一下type(arg)是否精确等于某个注册过的类型——registry里真实从来没有Dog这个类型的独立条目,但process(Dog())依然真实正确地路由到了Animal的handler。真实的查找逻辑是沿着type(arg).__mro__(本系列很早验证过它决定多继承方法查找顺序的那条链)从最具体的类型往上走,逐个检查有没有为链上某个类型注册过handler,一旦找到第一个匹配就真实使用它——Dog自己没有注册,但它的MRO链条上真实包含Animal,找到了就用Animal的handler。这让singledispatch真实具备了一种"面向类型继承体系"的分派能力:只需要给一个共同的基类注册一次handler,所有子类只要没被单独覆盖,都能真实自动共享这份逻辑,不需要给每一个具体子类都重复注册。

验证:这套灵活的类型查找真实不是零成本的

实测 · 本机执行输出
singledispatch call: 0.1229 s for 500000 calls
plain function call: 0.0369 s for 500000 calls
真实倍数: 约3.3倍

真实测出的这个差距,跟前一节验证的MRO查找机制直接对应:普通函数调用真实只是一次固定的函数指针跳转,singledispatch每次调用真实都要先看第一个参数的类型,再真实沿着它的MRO链条查找registry(内部有缓存优化重复查找同一类型的开销,但本文这次每次都是process(5)同一种类型,缓存命中之后依然比直接调用慢),这份类型分派的判断逻辑真实是有代价的。这跟本系列前面讲的很多"更灵活但更贵"的机制是同一类真实取舍:singledispatch提供的价值是可以用比一大串isinstance判断更清晰的方式组织"根据类型分派逻辑"这件事,还能真实借助MRO自动覆盖子类,代价是每次调用真实比一次朴素的直接函数调用慢上几倍——对于调用频率不是极端高的场景(比如序列化、格式化这类偏应用层的逻辑),这份代价通常真实换来的可读性和可扩展性是值得的,但如果这个函数真实在每秒百万次调用的热路径上,这份开销值得留意。

分步演示:从子类自动命中父类handler到量出来的真实开销

每一步都是本文上面用真实的functools.singledispatch跑出来的原始结果。

singledispatch真实沿MRO查找,以及这份灵活性的真实代价
这一步新验证的机制

    本文用到的工具

    • 本机 Python 3.14.7,标准库 functools.singledispatch
    京ICP备2026067502号-1京公网安备11010502063487号