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