Python singledispatch:第二个参数不参与选路,bool 也会走整数分支
给一种操作安排多种实现
写一个小型导出器时,数字要补零,文字要原样保留,布尔值要显示开关状态。开始用几个判断就够了,但类型不断增加后,可以把同一种操作的实现分别注册到 singledispatch。调用入口仍是一个函数,选用哪段实现取决于第一个实参的运行时类型,规则比函数名字或注解内容更具体。
先用一个刻意不完整的版本观察边界。默认实现拒绝未知类型,整数实现负责补零。此时传入 True 也会进入整数分支,因为 bool 是 int 的子类。这个结果不是把字符串猜成数字,也不是比较 True 与一是否相等,而是沿类型继承关系找到了可用实现。
先观察回退,再补上布尔规则
下面的代码在 Python 3.12.14 验证,把它保存为 demo.py 后执行即可。注册装饰器显式写出类型,便于看清选路条件;后面的 width 是同一实现接收的普通参数。第一次布尔调用打印 01,随后注册 bool 专用实现,再次调用打印 ON,两个阶段都用断言固定了预期。
把 width 从整数改成字符串,不会换另一条分支。例子里的整数实现仍会收到它,再按自身契约报 TypeError。这说明分派负责挑选函数,参数校验仍由被选中的实现承担。注册 bool 后,dispatch(bool) 可以直接检查选中了哪个函数,无需从复杂输出反推。
AI概念示意图:输入经过类型分派器进入不同形状的处理入口,嵌套圆形提示继承关系。图片用于解释概念,不是运行截图。
from functools import singledispatch
@singledispatch
def display(value, width=2):
raise TypeError(f"unsupported: {type(value).__name__}")
@display.register(int)
def display_int(value, width=2):
if type(width) is not int or width < 1:
raise TypeError("width must be a positive integer")
return f"{value:0{width}d}"
print("before bool rule:", display(True))
assert display(True) == "01"
assert display.dispatch(bool) is display_int
@display.register(bool)
def display_bool(value, width=2):
return "ON" if value else "OFF"
@display.register(str)
def display_text(value, width=2):
return value
rows = [display(7), display(True), display(False), display("7")]
print(rows)
assert rows == ["07", "ON", "OFF", "7"]
assert display.dispatch(bool) is display_bool
try:
display(7, "2")
except TypeError:
print("bad width rejected")
else:
raise AssertionError("expected width rejection")
try:
display(1.5)
except TypeError:
print("unsupported type rejected")
else:
raise AssertionError("expected type rejection")让默认实现暴露缺失的约定
正式输出是 07、ON、OFF 和原样的 7。随后两行确认错误宽度和未支持的浮点数被拒绝。这两种失败来自不同位置:前者已经选中整数实现,后者落到通用默认实现。记录错误时保留这一区别,能更快判断究竟是调用参数不合适,还是插件少注册了一类对象。
默认函数也可以做通用转换,但在导出格式必须一致时,明确拒绝未知类型更容易发现遗漏。不要让所有未知对象自动变成字符串后,再在下游猜回含义。新增实现最好验证输出约定、异常类型和边界值,例如零、负数、空字符串,以及本例特别检查的布尔值。
容器内容不属于分派依据
注册 list 只能辨认外层是列表,不会继续查看里面装的是整数还是文字。list[int] 这类注解描述内容意图,也不会让 singledispatch 自动按元素类型挑选实现。需要区分内容时,可以在列表实现内校验,或把数据封装成明确的业务类型,避免把一次首参数分派想象成完整的类型系统。
同样的限制也影响接口设计:如果真正决定行为的是第二个参数,应考虑调整参数顺序,或者直接写清楚的条件分支。实例方法还有 singledispatchmethod,规则和普通函数不能机械混用。这个工具适合稳定的一项操作逐步接纳新类型,只有两三条简单规则时,普通判断往往更容易阅读。


