Python decorators

I have been learning about decorators recently. It is interesting functionality, but also one of those areas where the issues just keep appearing, the more you look at it.

There are some good articles on it here and here

To my mind, a key requirement of decorators, after the need to be useful, is to be as unintrusive as possible. So the decorated function or method, and more particularly, users of the decorated function or method should not notice the decoration.

Some of the issues, in order of increasing complexity:

  • functions and methods
  • return values
  • function / method arguments and keyword arguments
  • attributes such as __name__ and __doc__
  • decorator arguments
  • multiple decorators
  • introspection

I don’t have all the answers yet, and I’ll write more in a later post.

An example implementation shows some of the points of interest. This is a decorator with arguments implemented using a function.

from functools import wraps

def decoratorWithArguments(decoratorArgument = None):
    def wrap(functionOrMethod):
        # Support naive introspection
        @wraps(functionOrMethod)
        def wrappedFunctionOrMethod(*args, **kwargs):
            print("Before %s" % str(decoratorArgument) )
            ret = functionOrMethod(*args, **kwargs)
            print("After %s" % str(decoratorArgument) )
            return ret

        return wrappedFunctionOrMethod
    return wrap


@decoratorWithArguments("Decorator Argument Value")
def functionExample():
    """ functionExample docstring """
    return "functionExample"


class ClassExample:
    @decoratorWithArguments()  # defaults to None
    def methodExample(self):
        """ methodExample docstring """
        return "methodExample"


print(functionExample())
classInstance = ClassExample()
print(classInstance.methodExample())

Notes:

  • the wraps function from functools copies across various attributes such as the docstring.
  • *args and **kwargs should cope with both positional arguments and keyword arguments
  • Because decorators work differently with and without arguments, even though this decorator will work without any arguments, I still need to use () on line 22.

It took me an age to understand how decoratorWithArguments could possibly work. It’s a bit like Russian dolls:

  • The outer function, decoratorWithArguments, takes the decorator arguments. It returns the inner function, wrap (a closure).
  • Having unwrapped the outer function, we now have the returned inner function (wrap). This can then be called with the function or method that we want to decorate. It returns the inner function, wrappedFunctionOrMethod.
  • wrappedFunctionOrMethod can then be called. It will be called in place of the original decorated function or method.

Note that the decoration process takes care of all of the above for us. It’s still nice to understand how it can work.

About these ads
This entry was posted in Python, Software and tagged . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s