Skip to content

Instantly share code, notes, and snippets.

@mypy-play
Created June 8, 2026 01:06
Show Gist options
  • Select an option

  • Save mypy-play/2fb651ddee58d4176237eb3c3dfb7c1a to your computer and use it in GitHub Desktop.

Select an option

Save mypy-play/2fb651ddee58d4176237eb3c3dfb7c1a to your computer and use it in GitHub Desktop.
Shared via mypy Playground
from typing import Callable, Protocol
### Example 1: Works as intended. The method is decorated and is
### callable as usual.
def decorate_method[T](method: Callable[[T], None]) -> Callable[[T], None]:
def _inner(_self: T) -> None:
print('Decorated!')
method(_self)
return _inner
class A:
@decorate_method
def my_method(self) -> None:
print('my_method')
# Explicit call syntax. Fine.
print(A.my_method(A()))
# Instance call syntax. Fine.
print(A().my_method())
### Example 2: Instead of using Callable[[T], None], an equivalent
### protocol is declared. This is equivalent at runtime but mypy
### rejects it.
class MyCallable[T](Protocol):
def __call__(self, _self: T, /) -> None:
...
def decorate_method_1[T](method: MyCallable[T]) -> MyCallable[T]:
def _inner(_self: T) -> None:
print('Decorated!')
method(_self)
return _inner
class B:
@decorate_method_1
def my_method(self) -> None:
print('my_method')
# Explicit call syntax. Fine.
print(B.my_method(B()))
# Instance call syntax. Rejected by mypy (Too few arguments to
# __call__) but works at runtime.
print(B().my_method())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment