C 和 C 中的函数重载
我们在学习 C 和 C 的时候,会接触到一个概念叫做函数重载。简单来说函数重载指的是多个函数具有相同的名称,但是参数不同(包括参数类型和参数数量)。编译器在遇到重载函数的调用时,会在同名函数的不同重载实现中选择参数匹配的哪一个来调用。
这里举一个简单的例子。
代码语言:javascript复制#include <iostream>
void desc(int n){
std::cout << n << " is an integer" << std::endl;
}
void desc(double x){
std::cout << x << " is a floating number" << std::endl;
}
int main(){
desc(1);
desc(2.0);
return 0;
}
Python 中的函数重载
函数重载是一个很实用的语言特性,不过其他的编程语言大多没有提供函数重载的支持,包括 C#和 Java 这样的静态类型语言。
对于 Python 这门动态类型语言来说,传统上函数参数是不指定类型的,函数重载也就无从谈起。在 Python 中要实现根据不同参数类型来执行不同的逻辑,一般要使用条件判断。
代码语言:javascript复制from typing import Any
def desc(x: Any)->None:
if isinstance(x, int):
print(f'{x} is an integer')
elif isinstance(x, float):
print(f'{x} is a float')
else:
print(f'{x} is not an integer or float')
Python3.10 之后,可以使用 match case 语句替代 if 语句,使代码逻辑更清晰。
代码语言:javascript复制def desc(x: Any)->None:
match x:
case int(_):
print(f'{x} is an integer')
case float(_):
print(f'{x} is a float')
case _:
print(f'{x} is not an integer or float')
无论是使用 if 语句还是 match case 语句,随着需要处理的参数类型的种类增多,函数的复杂度也会显著提升,代码的可维护性也会下降。
使用functools.singledispatch
实现函数重载
事实上针对根据不同类型参数执行不同逻辑的场景,在 Python 中可以使用functools.singledispatch
来实现一定程度的函数重载。
from functools import singledispatch
@singledispatch
def desc(x: Any) -> None:
print(f'Just a {x}')
@desc.register(int)
def _(x) -> None:
print(f'{x} is an integer')
@desc.register(float)
def _(x) -> None:
print(f'{x} is a float')
首先使用singledispatch
装饰器装饰需要重载的函数,函数内可以提供一个默认实现。
随后使用func.register(type)
装饰器来注册不同类型参数的处理函数。
当被singledispatch
装饰函数被调用时,会根据参数类型来调用不同的处理函数,如果遇到没有特性实现的参数类型,会调用函数的默认实现。
使用类型注解
在上面的示例中,重载函数的类型是作为参数传到register
方法中的,随着 Python 类型注解机制的成熟和广泛使用,在 Python3.7 及以上的版本我们可以直接使用类型注解来定义重载函数的参数类型。
@singledispatch
def desc(x: Any) -> None:
print(f'Just a {x}')
@desc.register
def _(x: int) -> None:
print(f'{x} is an integer')
@desc.register
def _(x: float) -> None:
print(f'{x} is a float')
多参数函数的重载
functools.singledispatch
同样可以应用在多参数的函数中。
from functools import singledispatch
@singledispatch
def add(a, b):
return a b
@add.register
def _(a:list, b:list) -> list:
return [ea eb for ea, eb in zip(a, b)]
@add.register
def _(a:dict, b:dict) -> list:
keys = set([*a.keys(), *b.keys()])
return {key:a.get(key, 0) b.get(key, 0) for key in keys}
assert add(1, 2) == 3
assert add('a', 'b') == 'ab'
assert add([0, 1], [2, 3]) == [2, 4]
assert add({'a': 1, 'b': 2}, {'b': 3, 'c': 4}) == {'a': 1, 'b': 5, 'c': 4}
在上述代码实例中,我们定义了add
函数实现两个对象的假发,对于整数和字符串直接使用对应类型的加法逻辑(也就是add
函数的默认实现),并重载了列表和字典类型的add
函数实现,分别返回两个列表的逐项和两个字典相同键的值的和。
在业务代码中使用singledispatch
当业务逻辑足够复杂时,可以使用事件驱动模式将业务逻辑拆解为不同的事件。在处理不同事件时,传统模式可能会使用大量的分支判断,使用functools.singledispatch
可以简化事件的处理流程。
我们可以先定义基本的事件类和事件处理函数。
代码语言:javascript复制from dataclasses import dataclass
from functools import singledispatch
@dataclass
class Event:
ts:int
@singledispatch
def handle_event(event: Event) -> None:
raise NotImplementedError(f'Event type {type(event)} not supported')
然后根据具体业务需求,定义不同的事件类型,并为每个事件类型定义一个事件处理函数。
代码语言:javascript复制@dataclass
class LoginEvent(Event):
username: str
@dataclass
class LogoutEvent(Event):
username: str
@handle_event.register
def _(event: LoginEvent) -> None:
print(f'Logged in for user{event.username}')
@handle_event.register
def _(event: LogoutEvent) -> None:
print(f'Logged out for user {event.username}')
定义publish
函数用于处理事件的公共逻辑(例如日志记录和持久化到数据库),并将事件传递给handle_event
函数执行业务逻辑。
def publish(event: Event) -> None:
print(f'Publishing event: {event}')
handle_event(event)
在用户接口层(例如 REST 接口或消息队列的消息消费),只需要创建好对应的事件对象,然后调用publish
函数发布事件即可。
events = [LoginEvent('user0'), LoginEvent('user1'), LogoutEvent('user1'), LogoutEvent('user0')]
for event in events:
publish(event)
总结
functools.singledispatch
为 Python 提供了一种函数重载的实现方式,在代码中合理利用functools.singledispatch
可以有效地简化代码,提高代码的可读性和可维护性。