Мне кажется, что это невозможно сделать так чисто, как хотелось бы. Текущая система типов просто не может не иметь возможности связываться с аргументами.
Самое близкое, на что я смог прийти, использует два трюка: большая перегрузка для перечисления (вероятных) типов ввода и определение __get__
, чтобы отличить класс guish от доступа к экземпляру.
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 (undecorated class method): 'def (self: Any, a: builtins.float) -> builtins.bool'
w.bar (undecorated instance method): 'def (a: builtins.float) -> builtins.bool'
Widget.foo.__call__ (decorated class method): 'def (Any, builtins.float*) -> builtins.bool*'
w.foo.__call__ (decorated instance method): 'def (builtins.float*) -> builtins.bool*'
Это по крайней мере означает, что Mypy правильно разрешит Widget.foo(w, 2)
и w.foo(2)
и будет правильно disallow Widget.foo(w, "A")
и w.foo("A")
Однако в процессе он потерял имена параметров, и поэтому w.foo(a=2)
получает бесполезную ошибку "Неожиданный аргумент ключевого слова" a "".
Он также падает, если вы пытаетесь украсить метод слишком большим количеством аргументов, и, возможно, в некоторых странных крайних случаях.