使用 TypeVar 在 MyPy 中使用参数键入装饰器会产生预期的无人居住类型
Posted
技术标签:
【中文标题】使用 TypeVar 在 MyPy 中使用参数键入装饰器会产生预期的无人居住类型【英文标题】:Typing Decorator with Parameters in MyPy with TypeVar yields expected uninhabited type 【发布时间】:2017-08-18 04:38:51 【问题描述】: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 应该能够通过在wrapped
的末尾调用func
来推导出它确实是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(本文发布时的最新版本)
【问题讨论】:
【参考方案1】:哎呀!看来我搜索的不够仔细。已经有一个问题和解决方法:https://github.com/python/mypy/issues/1551#issuecomment-253978622
【讨论】:
TypeVar(F,...,Yeah)【参考方案2】:现在,mypy 直接支持这一点:
https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators
即
FuncT = TypeVar("FuncT", bound=Callable[..., Any])
def my_decorator(func: FuncT) -> FuncT:
@wraps(func)
def wrapped(*args: Any, **kwargs: Any) -> Any:
print("something")
return func(*args, **kwargs)
return cast(FuncT, wrapped)
【讨论】:
以上是关于使用 TypeVar 在 MyPy 中使用参数键入装饰器会产生预期的无人居住类型的主要内容,如果未能解决你的问题,请参考以下文章
将 mypy 的通用 self 与 python2 类型注释一起使用