Navigation

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.

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

@theDecoratordefsomeFunction(anArg):pass# some function body

We are doing the following:

We defined an argument function, someFunction().

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.

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.

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.

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.

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

importtypesclassSelfDocumenting(object):@classmethoddefgetMethods(aClass):return[(n,v.__doc__)forn,vinaClass.__dict__.items()iftype(v)==types.FunctionType]defhelp(self):"""Part of the self-documenting framework"""printself.getMethods()classSomeClass(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)

We import the types module to help
us distinguish among the various elements of a class
definition.

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.

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.

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.

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.

The result function, loggedFunc(), is
built when the decorator executes. This creates a fresh, new
function for each use of the decorator.

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.

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

classMyClass(object):@tracedef__init__(self,someValue):"""Create a MyClass instance."""self.value=someValue@tracedefdoSomething(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.

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.

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

Defines the argument function, someMethod().

Evaluate the abstract decorator debug(debugOption) to
create a concrete decorator based on the argument value.

Apply the concrete decorator the the argument function, someMethod().

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.

Merge the@traceand@debugdecorators.
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.

Create a@timingdecorator.
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.