Python singledispatch 单分派泛型函数详解与多分派实现

顾星河
顾星河
发布时间:2026-10-12| 2人浏览过
原创

本文深入解析 Python 标准库 functools 模块中的 singledispatch 装饰器,演示如何基于第一个参数类型实现单分派泛型函数。通过 register 方法注册不同类型处理逻辑,并探讨利用 isinstance 手动实现多分派泛型函数的技巧,同时澄清 Python 类型提示与真正泛型的区别。

@functools.singledispatch

该装饰器用于将普通函数转换为支持单一分派的泛型函数。

通过 @singledispatch 装饰一个函数,即可定义一个泛型函数。需要注意的是,泛型分派的依据是第一个参数的类型。

from functools import singledispatch
@singledispatch
def fun(arg, verbose=False):
    if verbose:
        print("Let me just say,", end=" ")
    print(arg)

利用泛型函数的 register() 属性可以注册新的实现以覆盖默认行为。register() 本身也是一个装饰器,对于带有类型注释的函数,它会自动匹配第一个参数类型为指定类型的重载函数。

@fun.register
def _(arg: int, verbose=False):
    if verbose:
        print("Strength in numbers, eh?", end=" ")
    print(arg)
@fun.register
def _(arg: list, verbose=False):
    if verbose:
        print("Enumerate this:")
    for i, elem in enumerate(arg):
        print(i, elem)

调用泛型函数时,系统会根据第一个参数的实际类型,分派并执行对应的具体实现函数。

当第一个参数为 int 类型时的执行结果:

>>> fun(42, verbose=True)
Strength in numbers, eh? 42

当第一个参数为 list 类型时的执行结果:

>>> fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)
Enumerate this:
0 spam
1 spam
2 eggs
3 spam

若通过 register() 装饰的函数未包含类型注释,则可以在 register() 调用时显式指定所需的类型参数。

@fun.register(complex)
def _(arg, verbose=False):
    if verbose:
        print("Better than complicated.", end=" ")
    print(arg.real, arg.imag)
>>>fun(6+5j, verbose=True)
Better than complicated. 6.0 5.0

为了注册已存在的函数或匿名函数,register() 还可以作为普通函数调用使用。

def nothing(arg, verbose=False):
    print("Nothing.")
    
fun.register(type(None), nothing)
fun.register(int,  lambda x, y, verbose=False: x+y) # 本人添加的,官网没有这个例子

注:经测试,若存在多个可分派的实现,泛型函数将选择定义顺序中最后注册的那个函数进行分派。

>>> fun(None)
Nothing.
>>>fun(1,2)
3

该 register() 属性返回一个未被装饰的原始函数,此函数用于激活装饰器的堆栈空间,并为其创建独立的测试运行单元。

>>>import decimal
>>> @fun.register(float)
... @fun.register(decimal.Decimal)
... def fun_num(arg, verbose=False):
...     if verbose:
...         print("Half of your number:", end=" ")
...     print(arg / 2)
...
>>> fun_num is fun
False

如果针对特定具体类型未注册对应的实现函数,泛型函数将回溯查找更通用的实现。由于使用 @singledispatch 装饰的原始函数默认注册了 object 类型,因此在找不到更匹配的实现时,将回退调用原始函数。

注:此示例由作者提供。

>>>fun(bool,verbose=True)
Let me just say, <class 'bool'>

可以使用只读属性 registry 来查看当前已注册的所有类型及其对应的函数实现。

>>> fun.registry.keys()
dict_keys([<class 'object'>, <class 'decimal.Decimal'>, <class 'float'>, <class 'int'>, <class 'list'>, <class 'complex'>, <class 'NoneType'>])
>>> fun.registry
{<class 'object'>: <function fun at 0x00000225F21AC268>, <class 'decimal.Decimal'>: <function fun_num at 0x00000225F2517378>, <class 'float'>: <function fun_num at 0x00000225F2517378>, <class 'int'>: <function <lambda> at 0x00000225F2596488>, <class 'list'>: <function _ at 0x00000225F25172F0>, <class 'complex'>: <function _ at 0x00000225F2596400>, <class 'NoneType'>: <function nothing at 0x00000225F258E400>}
>>> fun.registry[float]
<function fun_num at 0x00000225F2517378>
>>>fun.registry[object]
<function fun at 0x00000225F21AC268>

官方文档链接:https://docs.python.org/3/library/functools.html?highlight=functools wraps#functools.update_wrapper

singledispatch 实现单分派泛型函数及多分派泛型函数

本次的主题是探索相对少见的泛型应用!

提到泛型,Java 开发者可能非常熟悉,其本质是参数化类型,即操作的数据类型被指定为一个参数。但对于 Python 开发者而言,这部分知识或使用经验可能较为陌生。

那么,在 Python 中如何实现泛型呢?

确实有一个库可以实现这一功能。

我们首先导入包含 singledispatch 的库:

from functools import singledispatch

该库仅支持针对函数的第一个参数进行泛型分派。

首先定义一个主函数并使用 singledispatch 进行修饰,作为基础实现。随后定义一些“子函数”,使用 @主函数名.register 作为修饰器,并传入一个参数作为类型判断依据(该参数即为子函数第一个参数的类型,也是主函数第一个参数的类型)。注意:这里的子函数通常无需命名,因为它们在泛型函数内部使用,且参数结构与主函数保持一致。

然而,这种单分派的局限性较大,实际应用价值有限,我们需要进一步推广到多分派泛型函数。

多分派泛型函数的实现方法(由于 Python 原生仅支持对第一个参数进行泛型判断,因此需要添加额外代码来实现多分派)。

我们在单分派的基础上,结合 isinstance 进行类型判断,以确保其他参数的类型一致性。

上述多分派泛型函数也可以采用以下写法:

每个子函数使用了两个修饰器,但这两个修饰器均针对第一个参数生效。

你以为这就结束了吗?

Python 3.5 引入了新特性——在参数后添加冒号指定类型,并在函数定义后使用 -> 指定返回值类型。

冒号表示该参数预期的类型,箭头表示函数的返回类型。

这虽然指定了参数类型,但即使传入的类型与冒号后指定的类型不一致,也不会报错(除非存在语法错误),因此这并不等同于真正的泛型。

但它与泛型存在关联,这涉及 register 修饰器的第二种用法。

省略 register 的参数,转而使用冒号符号进行类型指定。

内容完结。

以上为个人经验总结,希望能为大家提供参考,也希望大家多多支持本站。

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。