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

问题描述 投票:2回答:1

考虑下面的装饰器,它将任何二进制操作符扩展为多个参数。

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 将会有正确的docstrings(由于 @wraps decorator),但它们的签名和类型注释是不正确的。 例如

>>> help(fadd)

Gives

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).

python-3.x python-decorators type-hinting
1个回答
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 返回值的类型必须和它的参数相同(否则注释会出错)。

然而,我很乐意知道更好的解决方案。

© www.soinside.com 2019 - 2024. All rights reserved.