使用TypeVar在MyPy中使用参数键入装饰器会产生预期的无人居住类型

时间:2017-03-25 05:59:20

标签: python-3.x decorator python-decorators mypy

MyPy在Callable *args**kwargs方面存在一些问题,特别是有关装饰器的问题,详见:https://github.com/python/mypy/issues/1927

具体来说,对于没有参数只包装​​函数(并且不更改其签名)的装饰器,您需要以下内容:

from typing import Any, Callable, cast, TypeVar

FuncT = TypeVar('FuncT', bound=Callable[..., Any])

def print_on_call(func: FuncT) -> FuncT:
    def wrapped(*args, **kwargs):
        print("Running", func.__name__)
        return func(*args, **kwargs)
    return cast(FuncT, wrapped)

最后的cast()应该是不必要的(MyPy应该能够通过在func末尾调用wrapped来确定包裹的确是FuncT -> FuncT )。我可以忍受这个,直到它被修复。

然而,当您引入带参数的装饰器时,这会破坏性。考虑装饰者:

def print_on_call(foo):
    def decorator(func):
        def wrapped(*args, **kwargs):
            print("Running", foo)
            return func(*args, **kwargs)
        return wrapped
    return decorator

使用的是:

@print_on_call('bar')
def stuff(a, b):
    return a + b

我们可能会尝试输入它(使用Guido认可的无参数示例作为指导),如下所示:

from typing import Any, Callable, Dict, List, TypeVar

FuncT = TypeVar('FuncT', bound=Callable[..., Any])

def print_on_call(foo: str) -> Callable[[FuncT], FuncT]:
    def decorator(func: FuncT) -> FuncT:
        def wrapped(*args: List[Any], **kwargs: Dict[str, Any]) -> Any:
            print("Running", foo)
            return func(*args, **kwargs)
        return cast(FuncT, wrapped)
    return cast(Callable[[FuncT], FuncT], decorator)

这似乎是类型检查,但是当我们使用它时:

@print_on_call('bar')
def stuff(a: int, b: int) -> int:
    return a + b

我们遇到了一个令人讨厌的错误:

error: Argument 1 has incompatible type Callable[[int, int], int]; expected <uninhabited>

我对这怎么可能感到有点困惑。如PEP 484中所述,似乎Callable[[int, int], int]应该是Callable[..., Any]的子类型。

我认为这可能是在print_on_call的返回类型和aa参数以及返回类型decorator之间使用泛型之间的错误迭代,因此我将我的示例简化为最低限度(虽然不再是一个工作装饰者,但仍然应该进行类型检查):

from typing import Any, Callable, Dict, List, TypeVar

FuncT = TypeVar('FuncT', bound=Callable[..., Any])

def print_on_call(foo: str) -> Callable[[FuncT], FuncT]:
    return cast(Callable[[FuncT], FuncT], None)

然而,这仍会导致上述错误。只需#type: ignore即可,这是我可以做的事情,但不幸的是,由于这个问题,装饰这个装饰器的任何函数都有类型<uninhabited>,所以你开始失去类型安全性无处不在。

所有人都说(tl; dr):

如何使用参数键入装饰器(不修改函数的签名)?上面是一个bug吗?可以解决吗?

MyPy版本:0.501(此帖子的最新版本)

2 个答案:

答案 0 :(得分:1)

糟糕!看起来我没有足够的搜索。已存在问题和解决方法:https://github.com/python/mypy/issues/1551#issuecomment-253978622

答案 1 :(得分:0)

现在,这是由 mypy 直接支持的:

https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators

im[7, 3] = 0.80913063