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.
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:
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.
Python has a few built-in decorators.
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.
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 )
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')]
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
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
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.
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