Search code examples
pythonsingletondecorator

Python decorator in class can't be used for methods of other classes


i have written an event system in which functions and metheodes can register as listeners for events. i use a decorator for this. The whole thing worked without problems for functions, but it doesn't work for methods. The core of the system is an eventDispatcher singleton class, which also contains the following code for the decorator:

def register(self, event_type: str):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)

        self._listeners[event_type].append(func)
        return wrapper

    return decorator

here the application on a method:


class MidiOutput:
    def __init__(self, out_port: str):
        pass

    @EventDispatcher.register("play_note")
    def on_play_note(self, event):
        pass

as a result i get the following error message:

    @EventDispatcher.register("output_changed")
TypeError: EventDispatcher.register() missing 1 required positional argument: 'event_type'

what can i do to be able to use the decorator for methods as well?


Solution

  • I've been able to replicate your issue and fix it simply.

    Here is my fixed code:

    from functools import wraps
    
    
    class EventDispatcher:
        def __init__(self):
            self._listeners = {
                "play_note": []
            }
    
        def register(self, event_type: str):
            """
            Register a decorator that can be used to register a listener for the specified event type.
    
            :param event_type: The event type to listen for.
            :return: A decorator that can be used to register a listener for the specified event type.
            """
            def decorator(func):
                @wraps(func)
                def wrapper(*args, **kwargs):
                    return func(*args, **kwargs)
    
                self._listeners[event_type].append(func)
                return wrapper
    
            return decorator
    
    
    DispatcherInstance = EventDispatcher()
    
    
    class MidiOutput:
        def __init__(self, out_port: str):
            pass
    
        @DispatcherInstance.register("play_note")
        def on_play_note(self, event):
            print("works ok")
    

    There is a little bit of guesswork involved with the init method (which was not provided).

    But apart from that all I do is instantiate the EventDispatcher class in an object called DispatcherInstance on line 28:

    DispatcherInstance = EventDispatcher()
    

    And then use that specific instance to call the register method as a wrapper on the MidiOutput definition.

    Now while this does fix your issue, it is worth noting that doing so requires creating an EventDispatcher object. Thus any code calling this file, will create that object.

    So I would keep that object as simple as possible if this is the fix you chose to implement.