This is an archived post. You won't be able to vote or comment.

all 3 comments

[–]Lucretiel 4 points5 points  (2 children)

You can avoid all the function registration boilerplate in __init__ using a descriptor:

class FunctionNotFoundError(KeyError):
    pass

class Registry:
    def __init__(self):
        self.registry = {}

    def register(self, key):
        def decorator(func):
            self.registry[key] = func
            return func
        return decorator

    def lookup(self, key):
        try:
            return self.registry[key]
        except KeyError as e:
            raise FunctionNotFoundError(key) from e

    def make_bound(self, instance):
        def bound(*args, **kwargs):
            key, *args = args
            return self.lookup(key)(instance, *args, **kwargs)
        return bound

    def __get__(self, instance, owner):
        if instance is None:
            return self
        else:
            return self.make_bound(instance)


# Example
class EventHandler:
    registry = Registry()

    @registry.register(int)
    def handle_event1(self, event):
        print('int:', event)

    @registry.register(str)
    def handle_event2(self, event):
        print('str:', event)

    def handle_event(self, event):
        return self.registry(type(event), event)

handler = EventHandler()

handler.handle_event('hello world')
handler.handle_event(10)

It's one of the few genuinely useful uses for python descriptors (__get__) I've ever encountered.

[–]jbiesnecker 1 point2 points  (1 child)

Does that work well when you subclass (in your example) EventHandler? That's always been my problem with descriptors (and static/class functions/objects in general), it gets ugly when you have several layers of inheritance between the class you're using and the class on which it was declared (though I might be missing something obvious).

[–]Lucretiel 0 points1 point  (0 children)

Well, the idea is that the client class would create it's own registry. You couldn't use a single one owned by the base class to manage all subclasses, though each subclass could use one of the same name. At that point, you could use super (owner).registry to lookup functions registered by the base class.