Search code examples
pythondecoratorpython-decorators

Get class instance in callable attribute


I want to mark class methods with decorators in such a way that a GUI can be automatically built to interact with the class. The simplest case for this is a 'button' method, which requires no arguments.

Here is how I define the decorator:

class Button(Control):
    def __init__(self, callback: Callable, label: Optional[str] = None):
        super(Button, self).__init__()
        self.callback = callback
        self.label = label

    def __call__(self, *args, **kwargs):
        return self.callback(*args, **kwargs)


def button(label: Optional[str]):
    """Decorator to denote function as one-shot button press"""
    def decorator(f):
        obj = Button(f, label)
        return obj
    return decorator

class SignalGenerator(BaseMockInstrument):
    def __init__(self):
        super(SignalGenerator, self).__init__()

    @button('Reset')
    def reset(self):
        pass

a = SignalGenerator()
a.reset() #TypeError: reset() missing 1 required positional argument: 'self'
a.reset(a) # Works

But this doesn't work because reset is called without the self argument. TypeError: reset() missing 1 required positional argument: 'self'

I think this is happening because during the decorator call, reset is a function, but it is not a bound instance method (because how could it be when the object doesn't exist yet).

So, the instance isn't passed to the callback.

How do I fix the bug and have the callback appropriately bound, and is there a better way to approach the problem overall?


Solution

  • You can use descriptors [Python-docs] to get the instance Button accessed on.

    class Button(Control):
        def __init__(self, callback: Callable, label: Optional[str] = None):
            super(Button, self).__init__()
            self.callback = callback
            self.label = label
            self.bound = None
        def __get__(self, instance, _):
            self.bound = instance
            return self
        def __call__(self, *args, **kwargs):
            return self.callback(self.bound, *args, **kwargs)