Search code examples
pythonexceptionpython-decoratorskeyboardinterrupt

Is it possible to create a decorator inside a class?


Or maybe you can think of an alternative solution to what I'm trying to do. Basically I have a class with a bunch of functions that I want to wrap in try/except blocks to intercept the KeyboardInterrupt error as I have a function that tidily cleans up each of my functions.

Instead of putting huge try catch blocks in each function I figured I could create a decorator to do that, but I'm running into some issues. So far I have something like this

class MyClass:
    def catch_interrupt(self, func):
        def catcher():
            try:
                func()
            except KeyboardInterrupt:
                self.End()
        return catcher

    @catch_interrupt
    def my_func(self):
        # Start a long process that might need to be interrupted

    def End(self):
        # Cleans up things
        sys.exit()

The issue when I run this is that I get the error

TypeError: catch_interrupt() takes exactly 2 arguments (1 given)

Is this even possible? Is there a better way or should I really just put try/except blocks around each functions innards?


Solution

  • It is indeed possible to create a decorator inside a class, but your implementation is faulty:

    First of all, catch_interrupt() can't take self.

    @catch_interrupt
    def my_func(self):
        # Start a long process that might need to be interrupted
    

    is equivalent to

    def my_func(self):
        # Start a long process that might need to be interrupted
    my_func = catch_interrupt(my_func)
    

    Obviously this doesn't allow self.

    Secondly, your inner wrapper function that you return from the decorator needs to at least take self as an argument and pass it on to func, as the functions you will be decorating expect self as their first arguments.

    You may also want to call your internal decorator _catch_interrupt to hint that it's meant for internal usage. That won't prevent anyone from calling it, but it's good practice given the behavior will be incorrect if called on an instance of the class (e.g. MyClass().catch_interrupt() will attempt to decorate the MyClass instance itself, which you probably don't want).


    My suggestion, however, would be to implement a context manager instead and have it perform your cleanup. That is more Pythonic for the cases where you're just enclosing a group of statements, and if you implement it right you can actually use it as a decorator too.