TGViewer
About Python [ru] About Python [ru] @python_tesst · 6.45K subscribers
Post #2810 277
⁣Типизированные TypeVar и ParamSpec для generic-коллбэков: статическая проверка обработчиков событий без Protocol и лишних абстракций

Типизация коллбэков — вечная боль production-кода, особенно при реализации диспетчеров событий или callback-based middleware. Частая ошибка: использовать Callable[..., Any], теряя сигнатуру и получая TypeError на проде.

Проблема

def register(handler: Callable[..., Any]):
...


Здесь анализатор не проверяет аргументы. В production это приводит к ошибкам при первом несовпадении типов — например, когда обработчик ожидает (int, str), а передается (float, User).

Решение

from typing import TypeVar, ParamSpec, Callable

P = ParamSpec("P")
T = TypeVar("T")

def register(
handler: Callable[P, T],
*args: P.args,
**kwargs: P.kwargs,
) -> T:
return handler(*args, **kwargs)


Статический анализатор теперь видит точную сигнатуру коллбэка. Никаких сюрпризов.

Реальный пример: диспетчер событий

class EventDispatcher:
def on(self, event: str) -> Callable[[Callable[P, T]], Callable[P, T]]:
def wrapper(handler: Callable[P, T]) -> Callable[P, T]:
self.handlers[event] = handler
return handler
return wrapper

dispatcher = EventDispatcher()

@dispatcher.on("user_login")
def handle_login(user_id: int, timestamp: float) -> str:
return f"User {user_id} logged in at {timestamp}"

# Правильно
result = dispatcher.handlers["user_login"](42, 1689000000.0)

# Ошибка типов: expected float, got str
result = dispatcher.handlers["user_login"](42, "bad")


Почему это лучше Protocol?

* Меньше кода — не надо объявлять абстрактные классы с __call__
* Точность — сохраняются имена параметров, порядок, типы и возвращаемое значение
* Универсальность — один декоратор работает с любыми сигнатурами
* Совместимость — работает с callback-based либами вроде functools или asyncio

Когда Protocol все же нужен?

Если коллбэк должен иметь атрибуты (например, handler.priority = 5) или наследовать несколько абстракций. По опыту — в 80% случаев это избыточно.

Вывод: ParamSpec + TypeVar дают чистую и проверяемую типизацию generic-коллбэков без лишних сущностей — замените Callable[..., Any] на точные сигнатуры на ревью.
  • ❤ 1
More from @python_tesst
  1. Sep 28, 2026Post #3191
  2. Sep 27, 2026OpenAI метит в подписку за 500 баксов Что там в описании тарифа? Пока что от ChatGPT Pro о…
  3. Sep 27, 2026Post #3189
  4. Sep 27, 2026Свежая обложка The Economist подъехала Журналисты: да мы вообще не сгущаем краски Те же жу…
  5. Sep 27, 2026Ночная годнота: Docker выкатила первые официальные скиллы для ИИ-агентов, которые ковыряют…
  6. Sep 27, 2026Post #3186
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →