There are many things why Pyhton is my standard go-to language if it comes to implement something. It’s either a website, automation, data-mining or complex calculation Python excels in most of it. I decided to write some of my favourite things which just makes things cleaner and easier to implement.
1. Class based decorators and context managers
I’ve already wrote a post where I mentioned the power of decorators. Now I want to take it to a step further. In spite of the most example can be found online decorators can be written as classes simply because everything in Python is an object. You just need to specify the __call__ method as you would do it with your function.
However sometimes you already have one (or more) decorator around your functions and it’s not really good to overdecorate your functions. Also you might not want to create a separate function because only small part of your method needs to be wrapped with the decorator’s behaviour. For example timeout. This is where context managers come into play.
Just like for examples Django’s transaction contexts your decorators can be written in a way that it can be used as a with
context as well.
Here is a simple example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
from utils import timeout from time import sleep # As decorator @timeout(seconds=3) def long_running_function(): print 'start' sleep(4) print 'finish' # As context def another_long_running_function(): print 'start' with timeout(seconds=3): sleep(4) print 'finish' |
In both cases we’re going to get timeout.TimeoutException.
So how to implement this? The only trick is you need the __enter__ and __exit__ for the context manager behaviour and __call__ for the decorator. Here’s a simple example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
import logging, signal class timeout(object): class TimeoutException(Exception): pass def __init__(self, seconds, msg=None): self.timeout = seconds self.timed_out = False self.msg = msg super(timeout, self).__init__() def _start(self): self.sig_handler = signal.signal(signal.SIGALRM, self.__handler) signal.alarm(self.timeout) def _cleanup(self): signal.signal(signal.SIGALRM, self.sig_handler) signal.alarm(0) def _timed_out(self): if self.msg: logging.warning('Timeout after %d seconds (%s)' % (self.timeout, self.msg)) else: logging.warning('Function timed out after %d seconds.' % self.timeout) def __enter__(self): self.start() logging.debug('Timeout context started with %d seconds.' % self.timeout) return self def __exit__(self, exc_type, exc_val, exc_tb): self._cleanup() if exc_type == timeout.TimeoutException: self.timed_out = True self._timed_out() return True def __call__(self, fn): def wrapped(*args, **kwargs): self._start() retval = None exception = None try: retval = fn(*args, **kwargs) except timeout.TimeoutException as e: self._timed_out() exception = e finally: self._cleanup() if exception: raise exception return retval return wrapped def __handler(self, signum, frame): raise timeout.TimeoutException() |
Recent comments