用于在Python中重载的装饰器

Pau*_*nta 7 python overloading decorator

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

isinstance在你的函数中进行一系列检查只是丑陋; 是否有任何函数装饰器可以启用函数重载?像这样的东西:

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

@overload(int)
def func(val):
    print('This is an int')
Run Code Online (Sandbox Code Playgroud)

更新:

以下是David Zaslavsky回答的一些评论:

通过一些修改[s],这将非常适合我的目的.我在实现中注意到的另一个限制是,由于您使用func.__name__字典键,因此您很容易在模块之间命名冲突,这并不总是令人满意的.[续]

[续]例如,如果我有一个模块超载func,另一个完全不相关的模块,也重载func,这些重载会发生碰撞,因为功能调度字典是全球性的.不知何故,该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)
Run Code Online (Sandbox Code Playgroud)

second.py

from first import concatenate

@concatenate.overload(float, str)
def concatenate(a, b):
    return str(a) + b
Run Code Online (Sandbox Code Playgroud)

Mar*_*osB 15

是的,类型库中有一个重载装饰器,可用于帮助使复杂的类型提示变得更容易。

from collections.abc import Sequence
from typing import overload


@overload
def double(input_: int) -> int:
    ...


@overload
def double(input_: Sequence[int]) -> list[int]:
    ...


def double(input_: int | Sequence[int]) -> int | list[int]:
    if isinstance(input_, Sequence):
        return [i * 2 for i in input_]
    return input_ * 2
Run Code Online (Sandbox Code Playgroud)

检查此链接了解更多详细信息。

刚刚注意到这是一个 11 年前的问题,抱歉再次提出。这是错误的。

  • 回答这个问题并没有错,因为它已经很旧了。相反,这很好,因为它已经过时了。你改进了很多。 (5认同)

Chr*_*ris 8

从 Python 3.4 开始,该functools模块现在支持@singledispatch装饰器。在您的情况下,这看起来像:

from functools import singledispatch


@singledispatch
def func(val):
    raise NotImplementedError


@func.register
def _(val: str):
    print('This is a string')


@func.register
def _(val: int):
    print('This is an int')
Run Code Online (Sandbox Code Playgroud)

用法

func("test") --> "This is a string"
func(1) --> "This is an int"
func(None) --> NotImplementedError
Run Code Online (Sandbox Code Playgroud)


Dav*_*d Z 5

快速答案: 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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

缺点包括

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

    @overload((str, int))
    def h():
        return 0
    
    Run Code Online (Sandbox Code Playgroud)

    并且在调用该函数时会出现错误。

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

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

    g('a', 7, bar=4.4)
    
    Run Code Online (Sandbox Code Playgroud)

    不起作用。

  • 如的定义中所述,涉及到许多嵌套括号g
  • 如注释中所述,这并不涉及在不同模块中具有相同名称的函数。

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