前言
最初认识这个库是因为 dayu_widgets 里面用到了这个进行函数的重载。
对于它能实现的效果还是挺感兴趣的。
singledispatch 可以实现函数的泛型重载
可以使用pip install singledispatch
安装使用, github 地址
下面是官方提供的案例整合
1 | from singledispatch import singledispatch |
可以看到 singledispatch 根据第一个参数的类型调用不同函数的功能。
这个功能在 Python 3.4 之后引入到 functools 里面。
原理分析
singledispatch 装饰器拆解
首先看装饰器 @singledispatch 的作用
装饰器整体代码可以简化为如下所示
1 | from functools import update_wrapper |
@singledispatch 包装函数之后返回 wrapper 对象
wrapper 同时添加register
&dispatch
方法
背后调用其实是将第一个参数的类型放到 dispatch 函数进行 调用分发。
因为
wrapper.register
有这个设置。
如果我们把 装饰器 的语法糖拆除就很清楚到底发生了什么
1 | # @singledispatch |
经过 装饰器 封装之后 fun 就会有
wrapper.register
方法了。
那只要register
方法也是一个装饰器的写法,就可以继续沿用@
装饰器的写法。
从这也可以理解为啥后续register
的函数命名已经不重要了。
regsiter 函数分析
1 | from functools import update_wrapper |
register
使用了非常取巧的方式构建带参数的装饰器。
如果 func 没有传参就返回一个lambda
来接收参数
然后会将当前获取的类型存放到registry
里面
get_cache_token
是获取ABCMeta._abc_invalidation_counter
的计数
因为使用了ABCMeta
元类会影响到 mro 判断,这里可以先抛开不提。
register
函数主要是给 registry 字典添加 类型 对应 func 的匹配关系
dispatch 函数分析
1 | from functools import update_wrapper |
WeakKeyDictionary 的用法可以参考这篇文章 链接
大概就是如果 key 的对象已经不存在的话,那么 WeakKeyDictionary 会自动清理这个键值对
为什么这里需要用到 WeakKeyDictionary ,因为
dispatch
传递的 cls 可能是用户扩展的类型。
用户也有可能处于某些原因直接删除了这个 cls 类型导致缓存出问题,所以用 WeakKeyDictionary 这种骚操作可以轻松解决问题。
应用场景如下 ↓↓↓
1 | from singledispatch import singledispatch |
在这种自定义扩展的情况下,需要根据 TestDict 类型的 mro 匹配出最符合条件的 registry 类型,调用相关的方法。
_find_impl
就是来干这个事情的。
_find_impl 剖析
1 |
|
_c3_mro
会根据就是根据_compose_mro
过滤的信息重新计算一遍 mro 顺序
mro 计算采用了 c3 算法,具体的计算过程可以参考 链接 Python官网
1 | 下面的伪代码比较好的阐明了 C3 算法的作用 |
不过这里的 singledispatch mro 还考虑 ABCMeta 元类的影响。
所以需要构建一个特殊的 c3 算法进行处理。
也直接导致 singledispatch 复杂了很多。
因为大多数情况下,很少会用到 Python 的 ABCMeta 进行编程。
所以 _compose_mro 大都是返回了 python __mro__ 的顺序
然后再从 mro 继承顺序里找出最匹配registry
存储对象的函数进行调用。
总结
singledispatch 用 Python 实现 c3 算法还挺有意思的,我这里就没有详细列出来了。
建议去看源码学习。