单分派泛函数
Posted May 31, 2021 by clannadzsy ‐ 2 min read
运行环境:下列两个完整代码块分别在 CPython 3.12.14 验证,仅需标准库;分别保存为 dispatch_basics.py、dispatch_add.py,执行 python 文件名.py 并对照输出注释。函数对象的内存地址会随运行变化,不作为输出一致性的判断依据。
singledispatch 是 PEP443 中引入的
- 单分派:根据一个参数的类型,以不同方式执行相同的操作的行为
- 多分派:可根据多个参数的类型选择专门的函数的行为
- 泛函数:多个函数绑在一起组合成一个泛函数
初步认识
functools.singledispatch 按第一个参数的运行时类型选择实现;没有精确注册时会按继承关系查找,最后回退到最初的 object 实现。register 返回注册前的函数,所以 fun_num is fun 为 False。
from decimal import Decimal
from functools import singledispatch
@singledispatch
def fun(arg):
print(arg)
@fun.register(str)
def _(arg):
print(type(arg), arg)
@fun.register(int)
def _(arg):
print(type(arg), arg)
@fun.register(list)
def _(arg):
print(type(arg), arg)
@fun.register(type(None))
def _(arg):
print(type(arg), arg)
@fun.register(float)
@fun.register(Decimal)
def fun_num(arg):
print(type(arg), arg)
if __name__ == '__main__':
fun("Hello, world.")
fun(123)
fun(['spam', 'spam', 'eggs', 'spam'])
fun(None)
fun(1.23)
fun(Decimal('1.23'))
# False
print(fun_num is fun)
# To access all registered implementations, use the read-only registry attribute
print(fun.registry.keys())
print(fun.registry[float])
print(fun.registry[object])
# 输出结果
# <class 'str'> Hello, world.
# <class 'int'> 123
# <class 'list'> ['spam', 'spam', 'eggs', 'spam']
# <class 'NoneType'> None
# <class 'float'> 1.23
# <class 'decimal.Decimal'> 1.23
# False
# dict_keys([<class 'object'>, <class 'str'>, <class 'int'>, <class 'list'>, <class 'NoneType'>, <class 'decimal.Decimal'>, <class 'float'>])
# <function fun_num at 0x00000155A9E9B790>
# <function fun at 0x00000155A972F0D0>
简单应用
下面的 check_type 是本例附加的限制:两个参数必须是完全相同的类型,并不是 singledispatch 自动完成了多参数分派。列表的 extend 和字典的 update 会修改第一个参数;字符串与元组会返回新对象。如果不希望修改调用者的列表或字典,应先传入副本。
from functools import singledispatch
def check_type(func):
def wrapper(*args):
arg1, arg2 = args[:2]
if type(arg1) != type(arg2):
return 'Error: 参数类型不同'
return func(*args)
return wrapper
@singledispatch
def add(obj, new_obj):
raise TypeError(f'不支持的首参数类型: {type(obj).__name__}')
@add.register(str)
@check_type
def _(obj, new_obj):
obj += new_obj
return obj
@add.register(list)
@check_type
def _(obj, new_obj):
obj.extend(new_obj)
return obj
@add.register(dict)
@check_type
def _(obj, new_obj):
obj.update(new_obj)
return obj
@add.register(tuple)
@check_type
def _(obj, new_obj):
return *obj, *new_obj
if __name__ == '__main__':
print(add('hello', ', world'))
print(add([1, 2, 3], [4, 5, 6]))
print(add({'name': 'xxx'}, {'age': 111}))
print(add(('1', '2',), ('3', '4',)))
# list 和 字符串 无法拼接
print(add([1, 2, 3], '4,5,6'))
# 输出结果
# hello, world
# [1, 2, 3, 4, 5, 6]
# {'name': 'xxx', 'age': 111}
# ('1', '2', '3', '4')
# Error: 参数类型不同
可将下面的检查追加到第二个文件。无注册实现的整数应抛出 TypeError;这里明确捕获它,避免把预期异常误当作合并示例失败。
values = [1]
assert add(values, [2]) is values
assert values == [1, 2]
assert add([1], '2') == 'Error: 参数类型不同'
try:
add(1, 2)
except TypeError as error:
print(error)
# 不支持的首参数类型: int