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.