Decorators

In addition to object-oriented programming, Python also supports an approach called Aspect-Oriented Programming . Object-oriented programming focuses on structure and behavior of individual objects. Aspect-oriented programming refines object design techniques by defining aspects which are common across a number of classes or methods.

The focus of aspect-oriented programming is consistency. Toward this end Python allows us to define “decorators” which we can apply to class definitions and method definitions and create consistency.

We have to note that decorators can easily be overused. The issue is to strike a balance between the obvious programming in the class definition and the not-obvious programming in the decorator. Generally, decorators should be transparently simple and so obvious that they hardly bear explanation.

We’ll look at what a decorator is in Semantics of Decorators.

We’ll look at some built-in decorators in Built-in Decorators.

In Defining Decorators we’ll look at defining our own decorators.

It is possible to create some rather sophisticated decorators. We’ll look at the issues surrounding this in Defining Complex Decorators.

Semantics of Decorators

Essentially, a decorator is a function that is applied to another function. The purpose of a decorator is to transform the function definition we wrote (the argument function) into another (more complex) function definition. When Python applies a decorator to a function definition, a new function object is returned by the decorator.

The idea of decorators is to allow us to factor out some common aspects of several functions or method functions. We can then write a simpler form of each function and have the common aspect inserted into the function by the decorator.

When we say

@theDecorator
def someFunction( anArg ):
    pass # some function body

We are doing the following:

  1. We defined an argument function, someFunction().
  2. Python applied the decorator function, theDecorator(), to our argument function. The decorator function will return a value; this should be some kind of callable object, either a class with a __call__() method or a function.
  3. Python binds the result of the decorator evaluation to the original function name, someFunction(). In effect, we have a more sophisticated version of someFunction() created for us by the theDecorator() function.

Cross-Cutting Concerns. The aspects that makes sense for decorators are aspects that are truly common. These are sometimes called cross-cutting concerns because they cut across multiple functions or multiple classes.

Generally, decorators fall into a number of common categories.

  • Simplifying Class Definitions. One common need is to create a method function which applies to the class-level attributes, not the instance variables of an object. For information on class-level variables, see Class Variables.

    The @staticmethod decorator helps us build method functions that apply to the class, not a specific object. See Static Methods and Class Method.

    Additionally, we may want to create a class function which applies to the class as a whole. To declare this kind of method function, the built-in @classmethod decorator can be used.

    If you look at the Python Wiki page for decorators (http://wiki.python.org/moin/PythonDecoratorLibrary), you can find several examples of decorators that help define properties for managing attributes.

  • Debugging. There are several popular decorators to help with debugging. Decorators can be used to automatically log function arguments, function entrance and exit. The idea is that the decorator “wraps” your method function with additional statements to record details of the method function.

    One of the more interesting uses for decorators is to introduce some elements of type safety into Python. The Python Wiki page shows decorators which can provide some type checking for method functions where this is essential.

    Additionally, Python borrows the concept of deprecation from Java. A deprecated function is one that will be removed in a future version of the module, class or framework. We can define a decorator that uses the Python warnings module to create warning messages when the deprecated function is used.

  • Handling Database Transactions. In some frameworks, like Django (http://www.djangoproject.org), decorators are used to simplify definition of database transactions. Rather than write explicit statements to begin and end a transaction, you can provide a decorator which wraps your method function with the necessary additional processing.

  • Authorization. Web Security stands on several legs; two of those legs are authentication and authorization. Authentication is a serious problem involving transmission and validation of usernames and passwords or other credentials. It’s beyond the scope of this book. Once we know who the user is, the next question is what are they authorized to do? Decorators are commonly used web frameworks to specify the authorization required for each function.

Built-in Decorators

Python has a few built-in decorators.

staticmethod(function) → function

The @staticmethod decorator modifies a method function so that it does not use any self variable. The method function will not have access to a specific instance of the class.

This kind of method is part of a class, but can only be used when qualified by the class name or an instance variable.

For an example of a static method, see Static Methods and Class Method.

classmethod(function) → function
The @classmethod decorator modifies a method function so that it receives the class object as the first parameter instead of an instance of the class. This method function wil have access to the class object itself.
property(fget[, fset, fdel, doc]) → function
The @property decorator modifies from one to three method functions to be a properties of the class. The returned method functions invokes the given getter, setter and/or deleter functions when the attribute is referenced.

Here’s a contrived example of using introspection to display some features of a object’s class.

introspection.py

import types

class SelfDocumenting( object ):
    @classmethod
    def getMethods( aClass ):
        return [ (n,v.__doc__) for n,v in aClass.__dict__.items()
                 if type(v) == types.FunctionType ]
    def help( self ):
        """Part of the self-documenting framework"""
        print self.getMethods()

class SomeClass( SelfDocumenting ):
    attr= "Some class Value"
    def __init__( self ):
        """Create a new Instance"""
        self.instVar= "some instance value"
    def __str__( self ):
        """Display an instance"""
        return "%s %s" % ( self.attr, self.instVar )
  1. We import the types module to help us distinguish among the various elements of a class definition.
  2. We define a superclass that includes two methods. The classmethod, getMethods(), introspects a class, looking for the method functions. The ordinary instance method, help(), uses the introspection to print a list of functions defined by a class.
  3. We use the @classmethod decorator to modify the getMethods() function. Making the getMethods() into a class method means that the first argument will be the class object itself, not an instance.
  4. Every subclass of SelfDocumenting can print a list of method functions using a help() method.

Here’s an example of creating a class and calling the help method we defined. The result of the getMethods() method function is a list of tuples with method function names and docstrings.

>>> ac= SomeClass()
>>> ac.help()
[('__str__', 'Display an instance'), ('__init__', 'Create a new Instance')]

Defining Decorators

A decorator is a function which accepts a function and returns a new function. Since it’s a function, we must provide three pieces of information: the name of the decorator, a parameter, and a suite of statements that creates and returns the resulting function.

The suite of statements in a decorator will generally include a function def statement to create the new function and a return statement.

A common alternative is to include a class definition statement . If a class definition is used, that class must define a callable object by including a definition for the __call__() method and (usually) being a subclass of collections.Callable.

There are two kinds of decorators, decorators without arguments and decorators with arguments. In the first case, the operation of the decorator is very simple. In the case where the decorator accepts areguments the definition of the decorator is rather obscure, we’ll return to this in Defining Complex Decorators.

A simple decorator has the following outline:

def myDecorator( argumentFunction ):
    def resultFunction( \*args, \*\*keywords ):
        enhanced processing including a call to argumentFunction
    resultFunction.__doc__= argumentFunction.__doc__
    return resultFunction

In some cases, we may replace the result function definition with a result class definition to create a callable class.

Here’s a simple decorator that we can use for debugging. This will log function entry, exit and exceptions.

trace.py

def trace( aFunc ):
    """Trace entry, exit and exceptions."""
    def loggedFunc( \*args, \*\*kw ):
        print "enter", aFunc.__name__
        try:
            result= aFunc( \*args, \*\*kw )
        except Exception, e:
            print "exception", aFunc.__name__, e
            raise
        print "exit", aFunc.__name__
        return result
    loggedFunc.__name__= aFunc.__name__
    loggedFunc.__doc__= aFunc.__doc__
    return loggedFunc
  1. The result function, loggedFunc(), is built when the decorator executes. This creates a fresh, new function for each use of the decorator.
  2. Within the result function, we evaluate the original function. Note that we simply pass the argument values from the evaluation of the result function to the original function.
  3. We move the original function’s docstring and name to the result function. This assures us that the result function looks like the original function.

Here’s a class which uses our @trace decorator.

trace_client.py

class MyClass( object ):
    @trace
    def __init__( self, someValue ):
        """Create a MyClass instance."""
        self.value= someValue
    @trace
    def doSomething( self, anotherValue ):
        """Update a value."""
        self.value += anotherValue

Our class definition includes two traced function definitions. Here’s an example of using this class with the traced functions. When we evaulate one of the traced methods it logs the entry and exit events for us. Additionally, our decorated function usees the original method function of the class to do the real work.

>>> mc= MyClass( 23 )
enter __init__
exit __init__
>>> mc.doSomething( 15 )
enter doSomething
exit doSomething
>>> mc.value
38

Defining Complex Decorators

A decorator transforms an argument function definition into a result function definition. In addition to a function, we can also provide argument values to a decorator. These more complex decorators involve a two-step dance that creates an intermediate function as well as the final result function.

The first step evaluates the abstract decorator to create a concrete decorator. The second step applies the concrete decorator to the argument function. This second step is what a simple decorator does.

Assume we have some qualified decorator, for example @debug( flag ), where flag can be True to enable debugging and False to disable debugging. Assume we provide the following function definition.

debugOption= True
class MyClass( object ):
    @debug( debugOption )
    def someMethod( self, args ):
         real work

Here’s what happens when Python creates the definition of the someMethod() function.

  1. Defines the argument function, someMethod().
  2. Evaluate the abstract decorator debug( debugOption ) to create a concrete decorator based on the argument value.
  3. Apply the concrete decorator the the argument function, someMethod().
  4. The result of the concrete decorator is the result function, which is given the name someMethod().

Here’s an example of one of these more complex decorators. Note that these complex decorators work by creating and return a concrete decorators. Python then applies the concrete decorators to the argument function; this does the work of transforming the argument function to the result function.

debug.py

def debug( theSetting ):
    def concreteDescriptor( aFunc ):
        if theSetting:
            def debugFunc( *args, **kw ):
                print "enter", aFunc.__name__
                return aFunc( *args, **kw )
            debugFunc.__name__= aFunc.__name__
            debugFunc.__doc__= aFunc.__doc__
            return debugFunc
        else:
            return aFunc
    return concreteDescriptor
  1. This is the concrete decorators, which is created from the argument, theSetting.
  2. If theSetting is True, the concrete decorator will create the result function named debugFunc(), which prints a message and then uses the argument function.
  3. If theSetting is False, the concrete descriptor will simply return the argument function without any overhead.

Decorator Exercises

  1. Merge the @trace and @debug decorators. Combine the features of the @trace decorator with the parameterization of the @debug decorator. This should create a better @trace decorator which can be enabled or disabled simply.
  2. Create a @timing decorator. Similar to the parameterized @debug decorator, the @timing decorator can be turned on or off with a single parameter. This decorator prints a small timing summary.

Table Of Contents

Previous topic

Attributes, Properties and Descriptors

Next topic

Managing Contexts: the with Statement

This Page