Python 函数重载实现

Python 不支持函数重载,在同一个模块中声明同名方法不会报错,只会不停的覆盖,无论参数个数是否不同,最终只会保留最后一个函数
 1foo = 100
 2
 3def foo(a):
 4    print('foo(a)')
 5
 6def foo(a, b):
 7    print('foo(a, b)')
 8
 9def foo(a: str):
10    print('foo(a: str)')
11
12foo([8])
13print(globals()['foo'])
14foo(3, 5)

输出
foo(a: str)
<function foo at 0x107e74c10>
Traceback (most recent call last):
File ".../scratches/scratch_1.py", line 12, in <module>
foo(3, 5)
TypeError: foo() takes 1 positional argument but 2 were given
只有最后一个 foo 函数是存在的,在 globals() 只有一个 foo

那么 Python 要怎么实现函数重载呢?Python 3.4 在 functools 下添加了 singledispatch, Python 3.8 又继续加入了 singledispatchmethod。Python 的文档 https://docs.python.org/3/glossary.html#term-generic-functionPEP443 说它们的功用是支持单一分派的泛型函数。但本人觉得更像是函数重载。

为什么说是单分派(single dispatch)呢? 因为它只能根据第一个参数的类型选择要实际执行的函数。看下面的实例
 1from functools import singledispatch
 2
 3
 4@singledispatch
 5def foo(a):
 6    print(f"main {a}")
 7
 8print(hex(id(foo)))
 9
10@foo.register(int)
11def _foo(a):
12    print(f"{a} - 1")
13
14print(hex(id(_foo)))
15
16
17@foo.register(str)
18def _foo(a):
19    print(f"{a} - 2")
20
21print(hex(id(_foo)))
22
23
24foo(1)
25foo('str')
26foo([])
27breakpoint()

在 IDE 中我们可以在 breakpoint() 处打个断点来调试,或者运行直接进入  pdb 控制台查看运行状态

输出为
0x103dbd790
0x103dbd8b0
0x103dbd940
1 - 1
str - 2
main []
pdb
(Pdb) foo.registry
mappingproxy({<class 'object'>: <function foo at 0x103da2c10>, <class 'int'>: <function _foo at 0x103dbd8b0>, <class 'str'>: <function _foo at 0x103dbd940>})
(Pdb) foo
<function foo at 0x103dbd790>
(Pdb) _foo
<function _foo at 0x103dbd940>
从这里我们就能发现它的实现方式,singledispatch 装饰的函数 foo 用 @foo.register(cls) 来注册声明的方法,并存储在 foo.registry 属性中,调用 foo(x) 时便根据第一个参数的类型进行分派,默认会调用在 foo.registry 中的 <class 'object'> 对应的实现上,即代理到被 @singledispatch 装饰的函数的实现

singledispatchmethod 的用法

参考 Python 官方文档的实例
 1class Negator:
 2    @singledispatchmethod
 3    def neg(self, arg):
 4        raise NotImplementedError("Cannot negate a")
 5
 6    @neg.register
 7    def _(self, arg: int):
 8        return -arg
 9
10    @neg.register
11    def _(self, arg: bool):
12        return not arg
13
14
15negator = Negator()
16print(negator.neg(1))
17print(negator.neg(True))

执行后输出为 
-1
False
那能不能根据构造实例时的类型来判断调用哪个 neg() 方法呢?大概想要实现的是
Negator(1).neg()  -> 得到 -1
negator(True).neg()  -> 得到 False
尝试写成下面那样
 1from functools import singledispatchmethod
 2
 3class Negator:
 4    def __init__(self, arg):
 5        self.arg = arg
 6
 7    @singledispatchmethod
 8    def neg(self, arg):
 9        raise NotImplementedError("Cannot negate a")
10
11    @neg.register(int)
12    def _(self):
13        return -self.arg
14
15    @neg.register(bool)
16    def _(self):
17        return not self.arg
18
19print(Negator(1).neg())
20print(Negator(True).neg())

执行后出错

method = self.dispatcher.dispatch(args[0].__class__)
IndexError: tuple index out of range
所以函数还是必须要有至少一个参数才能代理到正确的方法实现上去,虽然注册方法是没有问题的
result = {function} <function Negator.neg at 0x107a82700>
registry = {mappingproxy: 3} {<class 'object'>: <function Negator.neg at 0x107a82430>, <class 'int'>: <function Negator._ at 0x107a82820>, <class 'bool'>: <function Negator._ at 0x107a828b0>}
永久链接 https://yanbin.blog/python-overload/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。