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()
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