python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > python单分派泛函数singledispatch

python基础之单分派泛函数singledispatch

作者:易辰_

这篇文章主要介绍了python基础之单分派泛函数singledispatch问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

python单分派泛函数singledispatch

singledispatch 是标准库 functools 模块的函数

可以把整体方案拆成多个模块,甚至可以为你无法修改的类提供专门的函数,使用 @singledispatch 装饰的函数会变成泛函数

1、 singledispatch :标记处理object类型的基函数

2、各个专门函数使用 @<<base_function>>.register(<<type>>) 装饰

3、专门函数的名称无关紧要, _ 是个不错的选择,简单明了

4、为每个需要处理的类型注册一个函数5、可以叠放多个 register 装饰器,让同一个函数支持不同类型

函数中使用

我们来看一个例子了解下:

from functools import singledispatch
from collections import  abc
@singledispatch
def show(obj):
    print (obj, type(obj), "obj")
#参数字符串
@show.register(str)
def _(text):
    print (text, type(text), "str")
#参数int
@show.register(int)
def _(n):
    print (n, type(n), "int")
#参数元祖或者字典均可
@show.register(tuple)
@show.register(dict)
def _(tup_dic):
    print (tup_dic, type(tup_dic), "int")
show(1)
show("xx")
show([1])
show((1,2,3))
show({"a":"b"})

输出如下:

1 <class 'int'> int
xx <class 'str'> str
[1] <class 'list'> obj
(1, 2, 3) <class 'tuple'> int
{'a': 'b'} <class 'dict'> int

好处是什么呢?类似于java的重载机制,可以在一个类中为同一个方法定义多个重载变体,比在一个函数中使用一长串的 if/elif

对象中使用

我们来看一个对象的例子

from functools import singledispatch
class abs:
    def type(self,args):
        ""
class Person(abs):
    @singledispatch
    def type(self,args):
        super().type("",args)
        print("我可以接受%s类型的参数%s"%(type(args),args))
    @type.register(str)
    def _(text):
        print("str",text)
    @type.register(tuple)
    def _(text):
        print("tuple", text)
    @type.register(list)
    @type.register(dict)
    def _(text):
        print("list or dict", text)
Person.type("safly")
Person.type((1,2,3))
Person.type([1,2,3])
Person.type({"a":1})
Person.type(Person,True)

输出如下:

str safly
tuple (1, 2, 3)
list or dict [1, 2, 3]
list or dict {'a': 1}
我可以接受<class 'bool'>类型的参数True

python的singledispatch装饰器

最近一直在学习装饰器的相关知识,学习到了functools中的singledispatch装饰器,记录一下

1.Python中不需要使用函数重载的原因

Python中一般是不需要使用函数的重载的。一般的静态语言例如C#是支持函数的重载的,为了就是多态以及代码的重用。

例如我们现在想要实现一个函数,它可以输出输入参数的类型,用C#函数的重载实现的代码如下

static void GetType(string input)
{
    Console.WriteLine("{0}是string类型", input);
}
static void GetType(int input)
{
    Console.WriteLine("{0}是int类型", input);
}
static void GetType(string[] input)
{
    Console.WriteLine("{0}是数组类型", input);
}
static void GetType(Dictionary<string,string> input)
{
    Console.WriteLine("{0}是字典类型", input);
}

此时如果想使用Python实现则不需要使用函数的重载,因为Python本身就是动态语言,不要在函数的参数中指定参数的类型,可以直接在函数体中判断变量的类型并且执行相应的语句即可:

def print_type(obj):
    """输出参数obj的类型"""
    if isinstance(obj, int):
        print(f"{obj}的类型是int")
    elif isinstance(obj, str):
        print(f"{obj}的类型是str")
    elif isinstance(obj, list):
        print(f"{obj}的类型是list")
    elif isinstance(obj, dict):
        print(f"{obj}的类型是dict")
    else:
        print(f"{obj}是其他类型")

2.Python中的泛函数以及singledispatch

上面简单的代码虽然使用Python也实现了功能,但是如果功能再复杂一点则我们需要写更多的if-elif语句,并且如果函数需要根据参数的类型来执行对应的子函数的话此时代码会更臃肿,并且不利于功能的扩展。

为此,在Python3.4以后在标准库中functools中加入了singledispatch装饰器。被singledispatch装饰的函数称为泛函数,由此实现的也被称为单分派泛函数。

其实在Python的标准库中就存在泛函数,例如len(),它就会根据传入参数的类型去读取相应的C结构体中对象的长度。但是在Python3.4以前用户是没有办法实现类似Pythonic的泛函数的。

并且有时候当使用不是自己编写的或者是无法修改的类的时候,我们需要向其中添加我们自定义的函数,此时就很难做到。但是有了singledispatch之后就会变得很容易。

下面为使用泛函数实现上述代码功能:

from functools import singledispatch
from collections import abc
import numbers
@singledispatch
def print_type_new(obj):  
    pass
@print_type_new.register(numbers.Integral)  
def _(n): 
    print(f"{n}的类型是Integral")
@print_type_new.register(str)
def _(text):
    print(f"{text}的类型是str")
@print_type_new.register(tuple)
@print_type_new.register(abc.MutableSequence)
def _(text):
    print(f"{text}的类型是Sequence")
@print_type_new.register(abc.Mapping)
def _(text):
    print(f"{text}的类型是Mapping")

使用singledispatch的时候,首先需要装饰一个基函数f(一般参数类型为object),之后需要为各个专门的函数使用类似于@f.register(type)之类的装饰器,并且要为每个需要特殊处理的类型注册一个函数,同时为了代码的兼容性,该专门函数应该尽可能的只处理抽象基类而不要处理具体实现(FluentPython P172)。

注意:

在Python如果需要根据传入参数的类型来让函数执行不同的操作,则此时应该将函数写成泛函数,即使用singledispatch装饰器。

注意singledispatch并不是重载,因为重载还有类似于参数类型相同,但是数量不同,这种类似的情况在Python中只需要使用可变参数*即可以解决。

singledispatch的引入本质是为了让用户可以自定义泛函数,以便可以处理在需要根据参数的类型做出相同操作的场合以及为第三方类添加自定义的函数,这一切都是为了提高程序的可扩展性。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文