Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type hinting callback functions

I am currently writing a system using events and event listeners and am running into a problem correctly typing the function signatures of the listeners. The relevant part of the program looks similar to

from typing import Hashable, Callable
from collections import defaultdict

class System:
    def __init__(self):
        self.event_listeners = defaultdict(list)

    def add_event_listener(self, event: Hashable, listener: Callable):
        self.event_listeners[event].append(listener)

    def emit_event(self, event: Hashable, *args, **kwargs):
        for listener in self.event_listeners[event]:
            listener(self, *args, **kwargs)

    ...

In addition to the instance of System itself, event related information can be passed to the event listeners as further arguments and the type of information (number and types of arguments) is dependent on the specific event.

Is there a way to type the defaultdict instance and the function add_event_listener to reflect that for every event there is a specific function signature Callable[[System, ...], object], that is expected? I thought about how this can be achieved using ParamSpec's but have not found a solution so far.

like image 373
cymin Avatar asked Sep 07 '25 08:09

cymin


2 Answers

Give typing.Protocol a shot.

from __future__ import annotations

from typing import Any, Hashable, Protocol
from collections import defaultdict


class EventListener(Protocol):
    def __call__(self, system: System, *args: Any, **kwds: Any) -> Any:
        ...


class System:
    def __init__(self) -> None:
        self.event_listeners: defaultdict[Hashable, list[EventListener]] = defaultdict(
            list
        )

    def add_event_listener(self, event: Hashable, listener: EventListener) -> None:
        self.event_listeners[event].append(listener)

    def emit_event(self, event: Hashable, *args: Any, **kwargs: Any) -> None:
        for listener in self.event_listeners[event]:
            listener(self, *args, **kwargs)
like image 69
Paweł Rubin Avatar answered Sep 10 '25 01:09

Paweł Rubin


I'm answering the question in your title Type hinting callback functions

Use Callable (from typing import Callable).

Structure:

Callable[[<PARAMETER TYPES>], <RETURN TYPE>]

Examples
def call(callback: Callable[[], int]) -> int:
  return callback()

def get1():
  return 1

def getA():
  return 'A'

call(get1) # ok
call(getA) # not ok
  • def foo(): pass has type Callable[[], None]
  • def foo(): return 1 has type Callable[[], int]
  • def foo(): return '1' has type Callable[[], str]
  • def foo(a: int): return '1' has type Callable[[int], str]
  • def foo(a: int, b: str): return '1' has type Callable[[int, str], str]
like image 36
DarkTrick Avatar answered Sep 10 '25 02:09

DarkTrick