用于Python重载的装饰器 [英] Decorator for overloading in Python

查看:73
本文介绍了用于Python重载的装饰器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道编写关心参数类型的函数并不是Pythonic,但是在某些情况下,根本无法忽略类型,因为它们的处理方式不同.

I know it's not Pythonic to write functions that care about the type of the arguments, but there are cases when it's simply impossible to ignore types because they are handled differently.

在函数中有一堆isinstance检查是很丑陋的.有没有可用的函数装饰器来启用函数重载?像这样:

Having a bunch of isinstance checks in your function is just ugly; is there any function decorator available that enables function overloads? Something like this:

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

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


更新:

我在 David Zaslavsky的答案上留下了一些评论:

Here's some comments I left on David Zaslavsky's answer:

进行一些修改后,这将非常适合我的目的.我在实现中注意到的另一个限制是,由于使用func.__name__作为字典键,因此模块之间容易发生名称冲突,这并不总是可取的. [继续]

With a few modification[s], this will suit my purposes pretty well. One other limitation I noticed in your implementation, since you use func.__name__ as the dictionary key, you are prone to name collisions between modules, which is not always desirable. [cont'd]

[继续]例如,如果我有一个模块使func重载,而另一个完全不相关的模块也使func重载,则这些重载将发生冲突,因为函数分派dict是全局的.应该以某种方式使该字典在模块本地.不仅如此,它还应该支持某种继承". [继续]

[cont.] For example, if I have one module that overloads func, and another completely unrelated module that also overloads func, these overloads will collide because the function dispatch dict is global. That dict should be made local to the module, somehow. And not only that, it should also support some kind of 'inheritance'. [cont'd]

[cont.]继承"的意思是:说我有一个带有某些重载的模块first.然后再有两个不相关的模块,但每个模块都导入first;这两个模块都将新的重载添加到了它们刚刚导入的现有重载中.这两个模块应该能够使用first中的重载,但是它们刚刚添加的新模块不应在模块之间相互冲突. (考虑到这一点,这实际上很难做对.)

[cont.] By 'inheritance' I mean this: say I have a module first with some overloads. Then two more modules that are unrelated but each import first; both of these modules add new overloads to the already existing ones that they just imported. These two modules should be able to use the overloads in first, but the new ones that they just added should not collide with each other between modules. (This is actually pretty hard to do right, now that I think about it.)

其中一些问题可以通过稍微更改装饰器语法来解决:

Some of these problems could possibly be solved by changing the decorator syntax a little bit:

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

推荐答案

快速解答::有一个重载包,尽管使用的语法略有不同,但它比我在下面描述的功能更强大.声明它仅适用于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.

这篇关于用于Python重载的装饰器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆