python的函数也可以实现重载

1. 借助functools.singledispatch实现重载

python的函数是一等公民,原则上或者说实质上并不像C++和java一样支持函数重载,但是可以借助functools.singledispatch装饰器来实现类似于函数重载的功能。functools.singledispatch 是Python的一个装饰器,它允许你将一个普通的函数变成一个单分派泛函数(generic function)。这意味着基于第一个参数的类型,你可以为同一个函数定义多个实现。它是Python对单分派泛型函数的支持,这种函数可以根据单个参数的类型来决定调用哪个具体的实现。

在讲解如何实现python函数的重载之前,有必要先解释重载所能解决的问题,下面的函数将根据参数的类型来实现不同的逻辑

def calc(x:Any, y:Any) -> Any:
    if isinstance(x, int):
        print("x和y都是int")
    elif isinstance(x, float):
        print("x和y都是float")
    else:
        raise NotImplementedError(f"calc not implemented for {type(x)} and {type(y)}")
    return x + y

在函数内部,通过对参数x的类型进行判断来决定走哪种处理逻辑,随着x类型的增加,if 语句的逻辑分支也就会增多,函数的复杂度会提升,降低了代码的可维护性,重载就能很好的解决这个问题,重载后的函数可以根据参数的类型自动决定调用某个具体的实现。

下面是一个示例

from functools import singledispatch
from typing import Any



@singledispatch
def calc(x: Any, y: Any) -> Any:
    raise NotImplementedError(f"calc not implemented for {type(x)} and {type(y)}")


@calc.register
def calc_int(x: int, y: int) -> int:
    print("x和y都是int")
    return x + y


@calc.register
def calc_float(x: float, y: float) -> float:
    print("x和y都是float")
    return x + y


calc(1, 2)

calc(3.4, 5.6)
  • 首先,我用singledispatch装饰器装饰函数calc,calc函数有两个参数,类型我标注为Any
  • 使用calc.register装饰器装饰函数calc_int 和 calc_float
  • 实际调用时,不是调用calc_int 或者 calc_float,而是调用calc, 具体实际执行哪个函数由传入的参数来决定,执行calc(1, 2) 时,调用的是calc_int函数,而执行calc(3.4, 5.6) 时调用的是calc_float函数,注意,是根据第一个参数的类型

如果传入的参数与注册的函数都不匹配,则会执行calc函数

calc('', 3)

程序会抛异常

NotImplementedError: calc not implemented for <class 'str'> and <class 'int'>

前面的示例中,被calc.register装饰的函数的参数都有清晰的类型标注,如果你不喜欢编写类型标注,也可以退而求其次,在register中约定类型,代码可以改写成下面的样子

from functools import singledispatch
from typing import Any



@singledispatch
def calc(x: Any, y: Any) -> Any:
    raise NotImplementedError(f"calc not implemented for {type(x)} and {type(y)}")


@calc.register(int)
def calc_int(x, y) -> int:
    print("x和y都是int")
    return x + y


@calc.register(float)
def calc_float(x, y) -> float:
    print("x和y都是float")
    return x + y


calc(1, 2)

calc(3.4, 5.6)

对于被calc.register装饰的函数,函数名称可以是相同的,因此你也可以写成下面的样子

@calc.register(int)
def _calc(x, y) -> int:
    print("x和y都是int")
    return x + y


@calc.register(float)
def _calc(x, y) -> float:
    print("x和y都是float")
    return x + y

2. 使用开源库multipledispatch

functools.singledispatch只能基于被装饰函数的第一个参数的类型来实现简单的派发,还不能算是真正的重载,想要根据函数的所有参数进行派发,可以使用开源库multipledispatch

pip install multipledispatch

使用示例如下

from multipledispatch import dispatch

@dispatch(int, int)
def calc(x, y) -> int:
    print("x和y都是int")
    return x + y


@dispatch(float, float)
def calc(x, y) -> float:
    print("x和y都是float")
    return x + y

@dispatch(float, int)
def calc(x, y):
    print("x是float,y是int")
    return x + y

calc(1, 2)

calc(3.4, 5.6)

calc(3.3, 5)

执行calc(3.3, 5) 时,会调用最后一个版本的calc的实现,singledispatch 无法实现这样的功能。

扫描关注, 与我技术互动

QQ交流群: 211426309

加入知识星球, 每天收获更多精彩内容

分享日常研究的python技术和遇到的问题及解决方案