Skip to content

Instantly share code, notes, and snippets.

@Floby
Created June 24, 2025 19:10
Show Gist options
  • Save Floby/01c19c349acf987ac38a19e9bf7fc093 to your computer and use it in GitHub Desktop.
Save Floby/01c19c349acf987ac38a19e9bf7fc093 to your computer and use it in GitHub Desktop.
from collections.abc import Callable
from typing import Concatenate, Generic, ParamSpec, Protocol, TypeVar
P = ParamSpec("P")
Owner = TypeVar("Owner")
Return = TypeVar("Return")
class Runner(Generic[Owner, P, Return]):
def __init__(self, owner: Owner, func: Callable[Concatenate[Owner, P], Return]):
self.func = func
self.owner = owner
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> Return:
return self.func(self.owner, *args, **kwargs)
class MyDescriptor(Generic[Owner, P, Return]):
def __init__(self, func: Callable[Concatenate[Owner, P], Return]):
self.func = func
def __get__(self, owner: Owner, type: type[Owner]) -> Runner[Owner, P, Return]:
return Runner(owner, self.func)
def with_descriptor(func: Callable[Concatenate[Owner, P], Return]) -> MyDescriptor[Owner, P, Return]:
return MyDescriptor(func)
class MyProtocol(Protocol):
def my_method(self, i: int) -> str: ...
class MyClass:
@with_descriptor
def my_method(self, i: int) -> str:
return f"hello {i}"
concrete_instance = MyClass()
reveal_type(concrete_instance.my_method) # W: Type of "concrete_instance.my_method" is "Runner[MyClass, (i: int), str]"
as_callable: Callable[[int], str] = concrete_instance.my_method # successful type checking
my_instance: MyProtocol = concrete_instance # type checking fails
reveal_type(my_instance.my_method) # W: Type of "my_instance.my_method" is "(i: int) -> str"
assert my_instance.my_method(8) == "hello 8"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment