且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

用于Python重载的装饰器

更新时间:2023-11-14 13:19:10

快速解答::有一个重载包,尽管使用的语法略有不同,但它比我在下面描述的功能更强大.声明它仅适用于Python 3,但看起来只需稍作修改(如果有,我还没有尝试过),就可以使其与Python 2兼容.

Quick answer: there is an overload package on PyPI which implements this more robustly than what I describe below, although using a slightly different syntax. It's declared to work only with Python 3 but it looks like only slight modifications (if any, I haven't tried) would be needed to make it work with Python 2.

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

Long answer: In languages where you can overload functions, the name of a function is (either literally or effectively) augmented by information about its type signature, both when the function is defined and when it is called. When a compiler or interpreter looks up the function definition, then, it uses both the declared name and the types of the parameters to resolve which function to access. So the logical way to implement overloading in Python is to implement a wrapper that uses both the declared name and the parameter types to resolve the function.

这是一个简单的实现:

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 should be called with two optional arguments, a tuple representing the types of all positional arguments and a tuple of tuples representing the name-type mappings of all keyword arguments. Here's a usage example:

>>> @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

其中的缺点

  • 它实际上并不检查装饰器所应用的功能是否与装饰器提供的参数兼容.你可以写

  • It doesn't actually check that the function the decorator is applied to is even compatible with the arguments given to the decorator. You could write

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

调用该函数时会出错.

它不能很好地处理与所传递的参数类型相对应的重载版本不存在的情况(这将有助于引发更具描述性的错误)

It doesn't gracefully handle the case where no overloaded version exists corresponding to the types of the arguments passed (it would help to raise a more descriptive error)

它可以区分命名参数和位置参数,所以类似

It distinguishes between named and positional arguments, so something like

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

不起作用.

我认为,所有这些都可以通过适当的摆弄加以补救.特别是,通过将调度表存储为从装饰器返回的函数的属性,可以轻松解决名称冲突的问题.就像我说的,这只是一个简单的示例,以演示如何做的基础.

All of these could be remedied with enough fiddling, I think. In particular, the issue of name collisions is easily resolved by storing the dispatch table as an attribute of the function returned from the decorator. But as I said, this is just a simple example to demonstrate the basics of how to do it.