Custom Search

Friday, April 2, 2010

How to Write a Decorator in python

How to Write a Decorator in python
===============

Decorators were added in Python 2.4 to make function and method wrapping (a
function that receives a function and returns an enhanced one) easier to read and
understand. The original use case was to be able to define the methods as class
methods or static methods, on the head of their definition. The syntax before the
decorators was:
>>> class WhatFor(object):
... def it(cls):
... print 'work with %s' % cls
... it = classmethod(it)
... def uncommon():
... print 'I could be a global function'
... uncommon = staticmethod(uncommon)
...
This syntax was getting hard to read when the methods were getting big, or several
transformations over the methods were done.
The decorator syntax is lighter and easier to understand:
>>> class WhatFor(object):
... @classmethod
... def it(cls):
... print 'work with %s' % cls
... @staticmethod
... def uncommon():
... print 'I could be a global function'
...
>>> this_is = WhatFor()
>>> this_is.it()
work with
>>> this_is.uncommon()
I could be a global function
When the decorators appeared, many developers in the community started to use
them because they became an obvious way to implement some patterns. One
of the original mail threads on this was initiated by Jim Hugunin, the IronPython
lead developer.

How to Write a Decorator
There are many ways to write custom decorators, but the simplest and most
readable way is to write a function that returns a sub-function that wraps the
original function call.
A generic pattern is:
>>> def mydecorator(function):
... def _mydecorator(*args, **kw):
... # do some stuff before the real
... # function gets called
... res = function(*args, **kw)

... # do some stuff after
... return res
... # returns the sub-function
... return _mydecorator
...
It is a good practice to give an explicit name to the sub-function like _mydecorator,
instead of a generic name like wrapper, because it will be easier to read tracebacks
when an error is raised in the chain: you will know you are dealing with the
given decorator.
When arguments are needed for the decorator to work on, a second level of
wrapping has to be used.

def mydecorator(arg1, arg2):
def _mydecorator(function):
def __mydecorator(*args, **kw):
# do some stuff before the real
# function gets called
res = function(*args, **kw)
# do some stuff after
return res
# returns the sub-function
return __mydecorator
return _mydecorator

Since decorators are loaded by the interpreter when the module is first read, their
usage should be limited to wrappers that can be generically applied. If a decorator
is tied to the method's class or to the function's signature it enhances, it should
be refactored into a regular callable to avoid complexity. In any case, when the
decorators are dealing with APIs, a good practice is to group them in a module that
is easy to maintain.

No comments:

Post a Comment