-
Notifications
You must be signed in to change notification settings - Fork 287
-
Related but seems a bit different: #1040, #1357.
I'm trying to type hint a decorator that should be applied to a method, and returns a different signature. I managed to get a working solution, with a small downside related to positional only arguments:
Code sample in pyright playground
from typing import Callable, TypeVar from abc import ABC, abstractmethod class Base(ABC): @abstractmethod def func(self, arg: int | str, /) -> int: pass BaseT = TypeVar("BaseT", bound=Base, contravariant=True) # used to type the `self` argument def handle_int(func: Callable[[BaseT, str], int], /) -> Callable[[BaseT, int | str], int]: # A wrapper would be defined to convert ints to strings ... class Impl(Base): @handle_int # Breaks if Base.func doesn't have the pos. only '/' marker def func(self, arg: str) -> int: return 1 def other(self): reveal_type(self.func) # (int | str) -> int
Using callback protocols, I'm able to make it work without having to define the base class as being pos. only:
Code sample in pyright playground
from typing import Callable, Protocol, TypeVar, Self, overload from abc import ABC, abstractmethod class Base(ABC): @abstractmethod def func(self, arg: int | str) -> int: pass BaseT = TypeVar("BaseT", bound=Base, contravariant=True) class BoundFuncProto(Protocol): def __call__(self, arg: int | str) -> int: ... class FuncProto(Protocol[BaseT]): def __call__(_self, self: BaseT, arg: int | str) -> int: ... # Trying to define `__get__`, as suggested in other discussions, and to make it work # with instance vs class access, but to no eval: @overload def __get__(self, obj: None, objtype: type[Base] | None = ..., /) -> Self: ... @overload def __get__(self, obj: BaseT, objtype: type[BaseT] | None = ..., /) -> BoundFuncProto: ... def handle_int(func: Callable[[BaseT, str], int], /) -> FuncProto[BaseT]: # A wrapper would be defined to convert ints to strings ... class Impl(Base): @handle_int def func(self, arg: str) -> int: # "func" overrides method of same name in class "Base" with incompatible type "FuncProto[Impl]" return 1 def other(self): reveal_type(self.func) # BoundFuncProto self.func(1) # type checks reveal_type(Impl.func) # FuncProto[Impl]
So I'm wondering if I'm missing anything in my FuncProto definition that would make it compatible with Base.func? Is using protocols the right path?
The first solution (returning a new Callable from the decorator) is way easier to implement, but it's inconvenient to have them considered as having the arguments pos. only.
Beta Was this translation helpful? Give feedback.