Event bus:
# eventbus.py
EventKey = Union[int, str]
listeners: Dict[EventKey, Set[Callable]] = defaultdict(set)
def on(event: EventKey):
    def decorator(callback):
        listeners[event].add(callback)
        
        @functools.wraps(callback)
        def wrapper(*args, **kwargs):
            return callback(*args, **kwargs)
        
        return wrapper
    return decorator
def emit(event: EventKey, *args, **kwargs):
    for listener in listeners[event]:
        listener(*args, **kwargs)
Example of a class that needs to listen to an event:
class Ticking(Referencable):
    
    def __init__(self, id_: int):
        super().__init__(id_)
    
    @eventbus.on(StandardObjects.E_TIMER)
    def on_clock(self, clock: Clock):
        match clock.id:
            case StandardObjects.TIME_TICK:
                self.on_time_tick(clock)
    
    def on_time_tick(self, clock: Clock):
        pass
Example of invoking the related event:
eventbus.emit(StandardObjects.E_TIMER, clock)  # clock is an instance of Clock
I'm trying to write a relatively simple global event bus in Python 3.11, however, I would like to register listeners to the bus via a decorator. The implementation below works fine when decorating functions, but falls over when a class method is decorated because of the "self" argument being missed when called:
Ticking.on_clock() missing 1 required positional argument: 'clock'
(I can confirm it's to do with "self" because modifying listener(*args, **kwargs) in emit() to listener('dummy', *args, **kwargs) throws the expected AttributeError: 'str' object has no attribute 'on_time_tick'.)
Then I explored ways to have the decorator somehow get a reference to the callback's class instance, but in Python 3, Callable objects longer have a means to access metadata about the class instance they belong to outside of unstable implementation-specific reflection hacks that I would certainly like to avoid.
 
    