Event Programming

Metaclass

class EventListener(type):
    listeners: dict[str, list[callable]] = {}

    @classmethod
    def register(cls, *clsnames):
        def wrapper(func):
            for clsname in clsnames:
                if clsname not in cls.listeners:
                    cls.listeners[clsname] = []
                cls.listeners[clsname] += [func]
        return wrapper

    def __new__(mcs, classname, bases, attrs):
        for listener in mcs.listeners.get(classname, []):
            listener.__call__(classname, bases, attrs)
        return type(classname, bases, attrs)


@EventListener.register('Astronaut')
def hello_class(clsname, bases, attrs):
    print(f'\n\nHello new class {clsname}\n')


@EventListener.register('Astronaut', 'Person')
def print_name(clsname, bases, attrs):
    print('\nNew class created')
    print('Classname:', clsname)
    print('Bases:', bases)
    print('Attrs:', attrs)


class Person(metaclass=EventListener):
    pass


class Astronaut(Person, metaclass=EventListener):
    pass

# New class created
# Classname: Person
# Bases: ()
# Attrs: {'__module__': '__main__', '__qualname__': 'Person'}
#
#
# Hello new class Astronaut
#
#
# New class created
# Classname: Astronaut
# Bases: (<class '__main__.Person'>,)
# Attrs: {'__module__': '__main__', '__qualname__': 'Astronaut'}

Implementation

class event:
    __slots__ = ('__subscribers', )

    def __init__(self):
        self.__subscribers = set()

    def call(self, *args, **kwargs):
        for subscriber in self.__subscribers:
            subscriber(*args, **kwargs)
    __call__ = call

    def register(self, function):
        self.__subscribers.add(function)
        return self
    __add__ = __iadd__ = register

    def unregister(self, function):
        self.__subscribers.remove(function)
        return self
    __sub__ = __isub__ = unregister

class EventManager:
    @staticmethod
    def register(name):
        if not hasattr(EventManager, name):
            setattr(EventManager, name, event())
        return getattr(EventManager, name).register

Use Case

@EventManager.register('on_foo')
def foo(*args, **kwargs):
    print('Args: ' + str(args), 'Kwargs: ' + str(kwargs))

def call_on_foo():
    EventManager.on_foo()
    EventManager.on_foo(1, 2, 3)
    EventManager.on_foo(a=1, b=2, c=3)
    EventManager.on_foo(1, 2, 3, a=1, b=2, c=3)

@EventManager.register('on_bar')
def bar():
    call_on_foo()

EventManager.on_bar()

Use

EventManager.on_bar + funkcja
EventManager.on_bar += funkcja

Assignments