考虑以下修饰符,该修饰符将任何二进制运算符扩展为多个参数:
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
以便创建分别乘和加其输入参数的imul
和fadd
函数。
函数imul
和fadd
将具有正确的文档字符串(由于@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
替换)。
答案 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
返回值必须具有与其参数相同的类型这一事实(否则注释将是错误的)。
但是,我很高兴知道更好的解决方案。