Python decorators

I’m not a big fan of Python (yet), but it certainly has some nice features, like ‘decorators’.

A decorator wraps a method and lets you do specific things before and after the method is called. This can be very useful, to check the state of the application before the method is called (for example if the user is logged in, has the permission to execute the method, etc.).

I recently had a class with various methods which had to connect to a remote service, do some stuff with the service, and then disconnect again to clear up the resources. I could have added the same connect and disconnect code to each of the methods, but using a decorator is of course much easier and maintainable. Here’s how it looked like:

I created a decorator ‘service_required’ which connects to the service and makes the service accessible to the class in which it is needed (see the ‘service’ variable). It is little bit like ‘dependency injection’ which Python doesn’t offer per se. Then it calls the decorated function. Thereafter it disconnects from the service again.

Note 1: The wrapper function takes the arguments ‘self’, ‘*args’, and ‘**kwargs’, that is ‘self’ a reference to the ‘calling’ class (that’s needed to set the ‘service’ variable of the ‘calling’ class), ‘*args’ and ‘**kwargs’ which are the arguments passed to the decorated methods.

Note 2: The decorator calls the decorated function, stores the returned value, does its cleanup tasks (disconnecting from the server) and then returns this value. This is important, otherwise the return value of the decorarated method is simply lost.

from functools import wraps


class Service():
    """
    This is an example service, which could be some remote
    service endpoint, a database, etc. In order to be used
    we must first connect to it and then after our request
    is finished we have to disconnect again, so that any
    resources are released again.
    """

    connected = False

    def connect(self):
        self.connected = True
        print "Connection established."

    def add_one(self, value):
        if not self.connected:
            raise Exception("This service is not online")
        return value + 1

    def disconnect(self):
        self.connected = False
        print "Connection closed."


def service_required(func):
    """
    Decorator which makes sure that the service is
    connected and gets properly disconnected again
    after a method call.
    """

    @wraps(func)
    def _wrapper(self, *args, **kwargs):
        self.service = Service()
        self.service.connect()

        value = func(self, *args, **kwargs)

        self.service.disconnect()
        self.service = None
        return value

    return _wrapper


class Example():
    """
    An example class which uses the service
    to do something.
    """

    service = None

    @service_required
    def do_something(self):
        value = self.service.add_one(1)
        print value

    @service_required
    def and_again(self):
        value = self.service.add_one(2)
        print value


example = Example()
example.do_something()
example.and_again()

Edit (2018/09/06)

I just noticed, an even better implementation is to wrap the function call in a try finally statement, this way you can be really sure that the close methods are called, even if the function call throws an exception. The only way the close method could be missed is if the function call terminates the application (either deliberatly or due to an error).

The decorator would then look like this:

def service_required(func):
    """
    Decorator which makes sure that the service is
    connected and gets properly disconnected again
    after a method call.
    """

    @wraps(func)
    def _wrapper(self, *args, **kwargs):
        self.service = Service()
        self.service.connect()

        try:
            return func(self, *args, **kwargs)
        finally:
            self.service.disconnect()
            self.service = None

    return _wrapper