装饰器在Python中重载

我知道编写关注参数类型的函数并不是Pythonic,但有些情况下,由于它们的处理方式不同,因此不可能忽略类型。

在你的函数中进行一堆isinstance检查只是丑陋的; 有没有可用的函数装饰器来启用函数重载? 像这样的东西:

@overload(str)
def func(val):
    print('This is a string')

@overload(int)
def func(val):
    print('This is an int')

更新:

以下是我在David Zaslavsky的回答中留下的一些评论:

通过一些修改,这将很适合我的目的。 在您的实现中我注意到了另一个限制,因为您使用func.__name__作为字典键,您很容易在模块之间发生名称冲突,这并不总是可取的。 [续]

例如,如果我有一个模块重载func ,另一个完全不相关的模块也重载func ,那么这些重载将发生冲突,因为函数dispatch dict是全局的。 不知何故,该字典应该在本地生成。 不仅如此,它还应该支持某种“继承”。 [续]

[继承]我的意思是说:我有一个模块, first有一些重载。 然后又有两个模块是不相关的,但每个模块都是first输入的; 这两个模块都将新的重载添加到他们刚刚导入的已有的重载中。 这两个模块应该能够使用重载在first ,但他们只是增加了新的不应该模块之间相互碰撞。 (这实际上很难做到,现在我想到了。)

有些问题可能通过修改装饰器语法来解决:

first.py

@overload(str, str)
def concatenate(a, b):
    return a + b

@concatenate.overload(int, int)
def concatenate(a, b):
    return str(a) + str(b)

second.py

from first import concatenate

@concatenate.overload(float, str)
def concatenate(a, b):
    return str(a) + b

快速回答:在PyPI上有一个重载包,它实现了比我下面描述的更强大的功能,尽管使用了稍微不同的语法。 它被宣称只能与Python 3一起工作,但它看起来只需稍作修改(如果有的话,我没有尝试过)就可以使它与Python 2一起工作。


长答案:在可以重载函数的语言中,函数的名称(无论是字面上的还是有效的)都通过有关其类型签名的信息来扩充,无论是函数的定义还是函数的调用时间。 当编译器或解释器查找函数定义时,它将使用声明的名称和参数类型来解析要访问的函数。 因此,在Python中实现重载的逻辑方法是实现一个使用声明名称和参数类型来解析函数的包装器。

这是一个简单的实现:

from collections import defaultdict

def determine_types(args, kwargs):
    return tuple([type(a) for a in args]), 
           tuple([(k, type(v)) for k,v in kwargs.iteritems()])

function_table = defaultdict(dict)
def overload(arg_types=(), kwarg_types=()):
    def wrap(func):
        named_func = function_table[func.__name__]
        named_func[arg_types, kwarg_types] = func
        def call_function_by_signature(*args, **kwargs):
            return named_func[determine_types(args, kwargs)](*args, **kwargs)
        return call_function_by_signature
    return wrap

应该使用两个可选参数调用overload ,一个元组表示所有位置参数的类型,以及一个元组元组表示所有关键字参数的名称类型映射。 以下是一个使用示例:

>>> @overload((str, int))
... def f(a, b):
...     return a * b

>>> @overload((int, int))
... def f(a, b):
...     return a + b

>>> print f('a', 2)
aa
>>> print f(4, 2)
6

>>> @overload((str,), (('foo', int), ('bar', float)))
... def g(a, foo, bar):
...     return foo*a + str(bar)

>>> @overload((str,), (('foo', float), ('bar', float)))
... def g(a, foo, bar):
...     return a + str(foo*bar)

>>> print g('a', foo=7, bar=4.4)
aaaaaaa4.4
>>> print g('b', foo=7., bar=4.4)
b30.8

缺点包括

  • 它实际上并不检查装饰器所应用的函数是否与给装饰器的参数兼容。 你可以写

    @overload((str, int))
    def h():
        return 0
    

    当函数被调用时你会得到一个错误。

  • 它不会优雅地处理不存在与传递的参数类型相对应的重载版本的情况(这将有助于引发更多的描述性错误)

  • 它区分名称和位置参数,所以类似

    g('a', 7, bar=4.4)
    

    不起作用。

  • 有很多嵌套的括号使用这个,就像g的定义一样。
  • 正如评论中所述,这不涉及不同模块中具有相同名称的函数。
  • 我想,所有这些都可以通过足够的摆弄来弥补。 特别是,通过将调度表存储为从装饰器返回的函数的属性,可以轻松解决名称冲突的问题。 但正如我所说的,这只是一个简单的例子来演示如何做到这一点的基础知识。


    这并不直接回答你的问题,但如果你真的想拥有一些类似于不同类型的重载函数的行为,并且(非常正确)不想使用isinstance,那么我会建议这样的:

    def func(int_val=None, str_val=None):
        if sum(x != None for x in (int_val, str_val)) != 1:
            #raise exception - exactly one value should be passed in
        if int_val is not None:
            print('This is an int')
        if str_val is not None:
            print('This is a string')
    

    在使用中,意图是显而易见的,甚至不需要不同的选项具有不同的类型:

    func(int_val=3)
    func(str_val="squirrel")
    
    链接地址: http://www.djcxy.com/p/56927.html

    上一篇: Decorator for overloading in Python

    下一篇: "Restful" Java WEB MVC frameworks