There are plenty of good explanations of python decorators (for example, 1, 2, 3, and 4), so I won’t try to improve on them here. Instead, I just want to show examples of three ways of defining a decorator:

  1. A simple decorator implemented as a wrapper function.
  2. A stateful decorator.
  3. A stateful decorator with decorator parameters.

As is often the case, python implements decorators using the Principle of Most Surprise, as we’ll see when looking at the differences between the second and third example. Nonetheless, decorators are pretty simple to implement, and can be useful.

1. A simple decorator implemented as a wrapper function

import logging

def logged(func):
  def wrapper(*args, **kwargs):'Entering %s' % func.__name__)
      result = func(*args, **kwargs)'Leaving %s' % func.__name__)
      return result
  return wrapper

This is about as simple as a decorator can get. The wrapper function just wraps the wrapped function with logging statements. The decorator itself returns the wrapper function.

Typical usage:

from logged import logged

def some_function(x, y):
    return x + y

2. A stateful decorator

This time we implement a decorator that maintains state at runtime. We’ll implement a decorator called ‘memoize’ which remembers the values returned by the wrapped function for each set of arguments that it’s called with.

To do this we’ll implement a decorator class1:

class memoize(object):
    def __init__(self, func):
        self._func = func
        self._memo = {}

    def __call__(self, *args, **kwargs):
        key = str(args) + str(kwargs)
        if key in self._memo:
            result = self._memo[key]
            print 'Using cached result for %s' % self.func.__name__
            return result
        result = self._func(*args, **kwargs)
        self._memo[key] = result
        return result

Here, the __init__ method is called at decorator time, i.e. at the point where the function is defined, and __call__ is called when the function is called.

3. A stateful decorator with decorator parameters

To pass parameters to the decorator (as opposed to the wrapped function), we need to do things a bit differently than above. In particular, the __call__ method will return a wrapped function, just as was the case with the simple function wrapper in the first example.

The example here is similar to memoize, but caches the function’s returned values only for a limited time, where that time is specified as the decorator parameter.

import time

class cached(object):
    def __init__(self, ttl=30):
        self._cache = {}
        self._ttl = ttl

    def __call__(self, func):
        def wrapped_func(*args, **kwargs):
            key = str(args) + str(kwargs)
            if key in self._cache:
                expires, value = self._cache[key]
                if time.time() < expires:
                    return value
            value = func(*args, **kwargs)
            self._cache[key] = (time.time() + self._ttl, value)
            return value
        return wrapped_func

So here, at decoration time (when the function is defined), __init__ is called, then immediately __call__ is called, returning the wrapped function.

That’s nice and all, but it doesn’t quite work

The trouble is that the decorators have overwritten both the name and the docstrings for the wrapped functions. Usually you won’t even notice this, but it will definitely cause problems for automated tools (like documentation extractors).

Fortunately, the python standard library functools module comes to the rescue. If you haven’t looked at functools you’re missing out: functools.partial is especially useful. But here we’ll take a quick look at functools.wraps.

One small problem with the decorators defined above is that they don’t play well with python reflection. for example,

def some_func(x):
  """Returns the square of x."""
  return x * x

print some_func.__name__
print some_func.__doc__

The output ought to be ‘some_func’ and ‘Returns the square of x.’, but is actually ‘wrapper’ and None. But we can modify the decorator as follows to fix this:

from functools import wraps
import logging

def logged(func):
  def wrapper(*args, **kwargs):'Entering %s' % func.__name__)
    result = func(*args, **kwargs)'Leaving %s' % func.__name__)
    return result
  return wrapper

This copies the name and doc attributes from the enclosed function to the wrapper function.

  1. After writing this code and beginning to write this post, I discovered that a version of memoize is implemented on the python wiki.