如何更正修饰后的函数签名和类型提示?

时间:2020-06-10 13:31:17

标签: python-3.x python-decorators type-hinting

考虑以下修饰符,该修饰符将任何二进制运算符扩展为多个参数:

from typing import Callable, TypeVar
from functools import reduce, wraps


T = TypeVar('T')


def extend(binop: Callable[[T, T], T]):
    """ Extend a binary operator to multiple arguments """

    @wraps(binop)
    def extended(*args: T) -> T:
        if not args:
            raise TypeError("At least one argument must be given")

        return reduce(binop, args)

    return extended

然后可以如下使用:

@extend
def fadd(x: float, y: float) -> float:
    """ Add float numbers """

    return x + y


@extend
def imul(x: int, y: int) -> int:
    """ Multiply integers """

    return x*y

以便创建分别乘和加其输入参数的imulfadd函数。

函数imulfadd将具有正确的文档字符串(由于@wraps装饰器),但是其签名和类型注释不正确。例如:

>>> help(fadd)

给予

fadd(x: float, y: float) -> float                                               
    Add float numbers  

>>> fadd.__annotations__                                              
{'x': <class 'float'>, 'y': <class 'float'>, 'return': <class 'float'>} 

这是不正确的。

实现装饰器以产生正确的函数签名的正确方法是什么?

我以某种方式认为,如果删除@wraps行,则类型提示和签名将是正确的。但是,事实并非如此。没有@wraps

>>> help(fadd)

给予

extended(*args: ~T) -> ~T  

(即,通用类型T不会被float替换)。

1 个答案:

答案 0 :(得分:0)

我发现使用inspect

的方法有点笨拙
def extend(binop: Callable[[T, T], T]):
    """ Extend a binary operator to multiple arguments """

    @wraps(binop)
    def extended(*args: T) -> T:
        if not args:
            raise TypeError("At least one argument must be given")

        return reduce(binop, args)

    sig = inspect.signature(extended)

    sig = sig.replace(
        parameters=[inspect.Parameter('args', inspect.Parameter.VAR_POSITIONAL,
                                      annotation=sig.return_annotation)]
    )

    extended.__signature__ = sig

    return extended

它不像我期望的那样优雅,尤其是因为它在很大程度上取决于binop返回值必须具有与其参数相同的类型这一事实(否则注释将是错误的)。

但是,我很高兴知道更好的解决方案。