我正在尝试为基于类的函数装饰器在类型注释中跟踪函数签名。这只是一个mypy存根项目:实际的实现将以不同的方式获得相同的结果。
所以,我们有一个像这样的基本装饰器骨架
from typing import Any, Callable, Generic, TypeVar
FuncT = TypeVar("FuncT", bound=Callable)
class decorator(Generic[FuncT]):
def __init__(self, method: FuncT) -> None:
... # Allows mypy to infer the parameter type
__call__: FuncT
execute: FuncT
带有以下存根示例
class Widget:
def bar(self: Any, a: int) -> int:
...
@decorator
def foo(self: Any, a: int) -> int:
...
w = Widget()
reveal_type(Widget.bar)
reveal_type(w.bar)
reveal_type(Widget.foo.__call__)
reveal_type(w.foo.__call__)
显示的类型如下:
Widget.bar (undecorated class method): 'def (self: demo.Widget, a: builtins.int) -> builtins.int'
w.bar (undecorated instance method): 'def (a: builtins.int) -> builtins.int'
Widget.foo.__call__ (decorated class method): 'def (self: demo.Widget, a: builtins.int) -> builtins.int'
w.foo.__call__ (decorated instance method): 'def (self: demo.Widget, a: builtins.int) -> builtins.int'
的含义是,如果我调用w.bar(2)
,它将通过类型检查器,但是如果我调用w.foo(2)
或w.foo.execute(2)
,则mypy会抱怨参数不足。同时,Widget.bar(w, 2)
Widget.foo(w, 2)
和Widget.foo.execute(w, 2)
都通过。
我想要的是一种对此进行注释的方法,以说服w.foo.__call__
和w.foo.execute
赋予与w.bar
相同的签名。
我似乎无法做到我想要的那样干净。当前的类型系统只是做not have the capability弄乱参数。
我能找到的最接近的方法有两个技巧:大量重载以枚举(可能的)输入类型,以及定义__get__
以区分类与实例访问。
class DecoratorCallable(Generic[FuncT]):#, ArbitraryCallable[FuncT]):
__call__ : FuncT
execute : FuncT
# FuncT and FuncT2 refer to the method signature with and without self
class DecoratorBase(Generic[FuncT, FuncT2]):
@overload
def __get__(self, instance: None, owner: object) -> DecoratorCallable[FuncT]:
# when a method is accessed directly, instance will be None
...
@overload
def __get__(self, instance: object, owner: object) -> DecoratorCallable[FuncT2]:
# when a method is accessed through an instance, instance will be that object
...
def __get__(self, instance: Optional[object], owner: object) -> DecoratorCallable:
...
# To support methods with up to 5 parameters, define 5 type vars.
# For more parameters, just keep counting.
T1 = TypeVar("T1")
T2 = TypeVar("T2")
T3 = TypeVar("T3")
T4 = TypeVar("T4")
T5 = TypeVar("T5")
R = TypeVar("R")
@overload
def decorator(f: Callable[[T1], R]) -> DecoratorBase[Callable[[T1], R], Callable[[], R]] :...
@overload
def decorator(f: Callable[[T1, T2], R]) -> DecoratorBase[Callable[[T1, T2], R], Callable[[T2], R]] :...
@overload
def decorator(f: Callable[[T1, T2, T3], R]) -> DecoratorBase[Callable[[T1, T2, T3], R], Callable[[T2, T3], R]] :...
@overload
def decorator(f: Callable[[T1, T2, T3, T4], R]) -> DecoratorBase[Callable[[T1, T2, T3, T4], R], Callable[[T2, T3, T4], R]] :...
@overload
def decorator(f: Callable[[T1, T2, T3, T4, T5], R]) -> DecoratorBase[Callable[[T1, T2, T3, T4, T5], R], Callable[[T2, T3, T4, T5], R]] :...
def decorator(f: Callable[..., R]) -> DecoratorBase[Callable, Callable]:
...
与以前相同的类,现在显示的类型是
Widget.bar(未修饰的类方法):'def(自己:任意,一个:builtins.float)-> builtins.bool'w.bar(未修饰的实例方法):'def(a:Builtins.float)-> Builtins.bool'Widget.foo。call(修饰的类方法):'def(任何,builtins.float *)-> builtins.bool *'w.foo。call(装饰实例方法):'def(builtins.float *)-> Builtins.bool *'
这至少意味着Mypy将正确允许Widget.foo(w, 2)
和w.foo(2)
,并且将正确禁止Widget.foo(w, "A")
和w.foo("A")
但是,在此过程中它丢失了参数名称,因此w.foo(a=2)
收到了无用的错误“意外的关键字参数“ a””。
[如果您尝试用太多的参数装饰方法,并且可能在某些奇怪的情况下,也会失败。