views:

444

answers:

2

How do I nicely write a decorator?

In particular issues include: compatibility with other decorators, preserving of signatures, ect.

I would like to avoid dependency on the decorator module if possible, but if there were sufficient advantages, then I would consider it.

Related

+3  A: 

Use functools to preserve the name and doc. The signature won't be preserved.

Directly from the doc.

>>> from functools import wraps
>>> def my_decorator(f):
...     @wraps(f)
...     def wrapper(*args, **kwds):
...         print 'Calling decorated function'
...         return f(*args, **kwds)
...     return wrapper
...
>>> @my_decorator
... def example():
...     """Docstring"""
...     print 'Called example function'
...
>>> example()
Calling decorated function
Called example function
>>> example.__name__
'example'
>>> example.__doc__
'Docstring'
Casebash
Its good ettiquete to make wiki answers to your own questions.
voyager
that does make sense
Casebash
+3  A: 

Writing a good decorator is no different then writing a good function. Which means, ideally, using docstrings and making sure the decorator is included in your testing framework.

You should definitely use either the decorator library or, better, the functools.wraps() decorator in the standard library (since 2.5).

Beyond that, it's best to keep your decorators narrowly focused and well designed. Don't use *args or **kw if your decorator expects specific arguments. And do fill in what arguments you expect, so instead of:

def keep_none(func):
    def _exec(*args, **kw):
        return None if args[0] is None else func(*args, **kw)

    return _exec

... use ...

def keep_none(func):
    """Wraps a function which expects a value as the first argument, and
    ensures the function won't get called with *None*.  If it is, this 
    will return *None*.

    >>> def f(x):
    ...     return x + 5
    >>> f(1)
    6
    >>> f(None) is None
    Traceback (most recent call last):
        ...
    TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
    >>> f = keep_none(f)
    >>> f(1)
    6
    >>> f(None) is None
    True"""

    @wraps(func)
    def _exec(value, *args, **kw):
        return None if value is None else func(value, *args, **kw)

    return _exec
Chris B.