参考链接:
Python methoddispatch包_程序模块 - PyPI - Python中文网
functools.singledispatchmethod(Python 3.8) - 知乎
摘要:本文章介绍了methoddispatch包的基本使用,以及如果在类外注册泛型函数,泛型函数的分派机制,通过属性调用查看指定类型会被分派的执行函数,查看泛型函数的所有注册函数,子类重写扩展父类的泛型函数,在单个实例上进行指定类型函数的重写等;简单提及python3.8中的functools.singledispatchmethod
1. methoddispatch包简介
python3.4向functools标准库中添加了singledispatch装饰器(主要针对函数),而methoddispatch 包则将此项功能添加到了实例方法中。
2. 什么是泛型函数?
泛型函数是指由多个函数组成的函数,可以对不同类型实现相同的操作,调用时应该使用哪个实现由分派算法决定
3. methoddispatch包的使用
from methoddispatch import singledispatch, SingleDispatch
from decimal import Decimal
class MyClass(SingleDispatch):
@singledispatch
def func(self, arg, verbose=False):
if verbose:
print("Let me just say,", end=" ")
print(arg)
@func.register(int)
def func_int(self, arg, verbose=False):
if verbose:
print("Strength in numbers, eh?", end=" ")
print(arg)
@func.register(list)
def func_list(self, arg, verbose=False):
if verbose:
print("Enumerate this:")
for i, elem in enumerate(arg):
print(i, elem)
@func.register(float)
@func.register(Decimal)
def func_num(self, arg, verbose=False):
if verbose:
print("Half of your number:", end=" ")
print(arg / 2)
若要定义泛型方法的定义,使用@singledispatch修饰;分派发生在第一个参数的类型上,相应的创建函数。要向函数添加重载实现,使用泛型函数的register()属性;它是一个装饰器,获取一个类型参数并装饰一个实现该类型操作的函数。
register()属性仅在类语句中工作,依赖于SingleDispatch.__init_subclass__创建实际调度表。这也意味着无法注册具有相同名称的两个方法,因为只有最后一个将出现在类字典中。
未定义在类中的函数想注册到泛型函数可以使用 add_overload 属性
def nothing(obj, args, verbose=False):
print("Nothing. ")
MyClass.func.add_overload(type(None), nothing)
当泛型函数被调用时,会根据传入的第一个参数的类型进行分派
>>> a = MyClass()
>>> a.func("Hello, world.")
Hello, world.
>>> a.func("test.", verbose=True)
Let me just say, test.
>>> a.func(42, verbose=True)
Strength in numbers, eh?
>>> a.func(['spam', 'spam', 'eggs', 'spam'], verbose=True)
Enumerate this:
0 spam
1 spam
2 eggs
3 spam
>>> a.func(None)
Nothing
>>> a.func(1.23)
0.615
如果特定类型没有注册的实现,则使用其方法解析顺序来查找更通用的实现。用@singledispatch修饰的原始函数注册为基object类型,这意味着如果找不到更好的实现,就使用它。
在检查泛型函数将为给定类型选择哪个实现,请使用dispatch()属性
>>> a.func.dispatch(flost)
<function MyClass.func_num at 0x000002C0E535F950>
>>> a.func.dispatch(dict)
<function MyClass.func at 0x000002C0E5355510>
要访问所有注册的实现,请使用只读的registry属性
>>> a.func.registry.keys()
dict_keys([<class 'object'>, <class 'int'>, <class 'list'>, <class 'decimal.Decimal'>, <class 'float'>, <class 'NoneType'>])
>>> a.func.registry[float]
<function MyClass.func_num at 0x000002236A7FF950>
>>> a.func.registry[object]
<function MyClass.func at 0x000002236A7F5510>
子类可以用它们自己的重写扩展基类上函数的类型注册表。SingleDispatch minix类确保每个子类都有自己独立的调度注册表副本
class SubClass(MyClass):
@MyClass.func.register(str)
def func_str(self, arg, verbose=False):
print("Str.")
>>> a = MyClass()
>>> a.func("hello")
hello
>>> b = SubClass()
>>> b.func("hello")
Str.
方法重写不需要再次提供 register 装饰器
class SubClass2(MyClass):
def func_int(self, arg, verbose=False):
print("Int")
>>> c = SubClass2()
>>> c.func_int(100)
Int
但是,为register decorator提供相同的类型也可以。用不同类型装饰一个方法重写(不是一个好主意)将被注册成不同的类型,并为原始类型保留基类处理程序。
如果需要,可以在单个实例上指定方法重写
def func_str(arg, verbose=False):
print("String.")
>>> d = MyClass()
>>> d.func.register(str, func_str)
>>> d.func("Hello! ")
String.
>>> e = MyClass()
>>> e.func("Hello! ")
Hello!
在Python3.6及更高版本中,对于用类型注释的函数,修饰符将自动推断第一个参数的类型
class MyClassAnno(SingleDispatch):
@singledispatch
def func(self, arg):
print("Default.")
@func.register
def func_int(self, arg: int):
print("Int.")
class SubClassAnno(MyClassAnno):
@MyClassAnno.func.register
def func_str(self, arg: str):
print("Str.")
在Python3.5和更早版本中,SingleDispatch 类使用一个元类SingleDispatchMeta来管理调度注册表。但是在python3.6和更高版本中,使用了__init_subclass__方法。如果您的类也继承自ABC接口,那么您可以在python3.5及更高版本中使用SingleDispatchABCMeta元类。
最后,通过类访问方法func, 将使用该类的调度注册表
>>> SubClass2.func(s, 1)
Subclass int
>>> MyClass.func(s, 1)
1
4. 在python3.8中,可以通过使用functools.singledispatchmethod
class Negator:
@singledispatchmethod
@classmethod
def neg(cls, arg):
raise NotImplementedError("Cannot negate a")
@neg.register
@classmethod
def _(cls, arg: int):
return -arg
@neg.register
@classmethod
def _(cls, arg: bool):
return not arg