Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Type hint a method decorator returning a different signature #1664

Unanswered
Viicos asked this question in Q&A
Discussion options

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.

You must be logged in to vote

Replies: 0 comments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Q&A
Labels
None yet
1 participant

AltStyle によって変換されたページ (->オリジナル) /