如何更正装饰函数签名和类型提示?

Posted

技术标签:

【中文标题】如何更正装饰函数签名和类型提示?【英文标题】:How to correct the decorated function signature and type hints? 【发布时间】:2020-09-29 22:40:50 【问题描述】:

考虑以下将任何二元运算符扩展到多个参数的装饰器:

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】:

我发现了一个使用 inspect 的稍微有点 hacky 的解决方案

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 返回值必须与其参数具有相同类型的事实(否则注释将是错误的)。

不过,我很乐意知道更好的解决方案。

【讨论】:

以上是关于如何更正装饰函数签名和类型提示?的主要内容,如果未能解决你的问题,请参考以下文章

保留装饰函数的签名

如何更正 VLOOKUP 函数中的 #N/A 错误

创建一个结合两个函数而不指定原始函数的调用签名的装饰器

如何在 PyCharm 中显示类型提示?

excel 里出现 #value? 如何解决呢?

在 Sphinx 文档中保留包装/装饰 Python 函数的默认参数