从可调用类型签名中删除自身以匹配实例方法

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

我正在尝试为基于类的函数装饰器在类型注释中跟踪函数签名。这只是一个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相同的签名。

python-3.x types mypy
1个回答
0
投票

我似乎无法做到我想要的那样干净。当前的类型系统只是做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 *'

这至少意味着M​​ypy将正确允许Widget.foo(w, 2)w.foo(2),并且将正确禁止Widget.foo(w, "A")w.foo("A")

但是,在此过程中它丢失了参数名称,因此w.foo(a=2)收到了无用的错误“意外的关键字参数“ a””。

[如果您尝试用太多的参数装饰方法,并且可能在某些奇怪的情况下,也会失败。

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