0
\$\begingroup\$

I'm new to pygame and tried to write my own Event Manager because I couldn't find a solution I was satisfied with. So I wrote my own inspired by the observer pattern:

from typing import Callable

from pygame.event import Event

EventHandler = Callable[[Event], None]

_managers = dict()


class EventManager:
    def __init__(self, name):
        self.name = name
        self.handlers: dict[int, list[EventHandler]] = dict()

    def notify(self, event: Event, selector=lambda event: event.type):
        eventType = selector(event)
        if eventType not in self.handlers:
            return

        for handler in self.handlers[eventType]:
            handler(event)

    def register(self, eventType: int, handler: EventHandler):
        print(f"{self.name} registered {eventType}")
        if eventType not in self.handlers:
            self.handlers[eventType] = [handler]
        else:
            self.handlers[eventType].append(handler)

    def deregister(self, eventType: int, handler: EventHandler):
        if not self._is_registered(eventType, handler):
            return
        self.handlers[eventType].remove(handler)

    def _is_registered(self, eventType, handler):
        return eventType in self.handlers and handler in self.handlers[eventType]

    @staticmethod
    def get(id: str):
        if id not in _managers:
            _managers[id] = EventManager(id)
        return _managers[id]


generalEventManager = EventManager.get("General Events")
keyEventManager = EventManager.get("Key Events")

it's used like this:

class Foo:
    _running = True

    def register_events(self):
        generalEventManager.register(pygame.QUIT, lambda _: self.on_quit())
        generalEventManager.register(pygame.KEYDOWN, lambda event: self.on_key_down(event))
        # register key events with
        # keyEventManager.register(pygame.K_RETURN, lambda _: self.on_k_return())
        # ...

    def on_key_down(self, event: Event):
        keyEventManager.notify(event, selector=lambda event: event.key)

    def on_quit(self):
        self._running = False

    def on_cleanup(self):
        pygame.quit()

    def on_execute(self):
        while self._running:
            for event in pygame.event.get():
                self.on_event(event)
        self.on_cleanup()

Is this pythonian? Are there better solutions in pygame? Are there generic python library solutions that I didn't find?

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

This seems fairly pythonic, and a decent use of the observer pattern.

One thing I would say is that you generally use class methods instead of static methods for alternate constructors, and then call the 'cls' attribute instead of calling the class by name. This is important if you ever want to make use of inheritance, in addition you may want to tie your _managers global dict to the class, for the same reason.

@classmethod
def get(cls, id: str):
    if id not in cls._managers:
        cls._managers[id] = cls(id)
    return cls._managers[id]

In addition, you don't need to pass lambdas in every time. You can instead just pass functions themselves.

def register_events(self):
    generalEventManager.register(pygame.QUIT, self.on_quit)  # You'll need to modify the call signature here
    generalEventManager.register(pygame.KEYDOWN, self.on_key_down)
    # register key events with
    # keyEventManager.register(pygame.K_RETURN, self.on_k_return)
    # ...

Your names are a little undescriptive, I'd probably call your 'get' method something like 'from_id' for clarity, plus obviously 'Foo' should be something richer.

I'd also look into better ways of handling some of your other higher order functions instead of leaning on lambdas. Maybe try using the operator module, for instance:

from operator import attrgetter

# Code here

    def notify(self, event: Event, selector=attrgetter('type')):
        ...

Also, you might want to use a default dict to simplify the implementation a bit:

from collections import defaultdict
from operator import attrgetter


class EventManager:
    def __init__(self, name):
        self.name = name
        self.handlers: dict[int, list[EventHandler]] = defaultdict(list)

    def notify(self, event: Event, selector=attrgetter('type')):
        for handler in self.handlers[selector(event)]:
            handler(event)

    def register(self, eventType: int, handler: EventHandler):
        print(f"{self.name} registered {eventType}")
        self.handlers[eventType].append(handler)

    def deregister(self, eventType: int, handler: EventHandler):
        if handler in self.handlers[eventType]:
            self.handlers[eventType].remove(handler)

    @classmethod
    def from_id(cls, _id: str):
        if _id not in _managers:
            _managers[_id] = EventManager(_id)
        return _managers[_id]
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.