[Tutor] decorators

Steven D'Aprano steve at pearwood.info
Fri Jun 24 05:05:23 CEST 2011


Robert wrote:
> Is there a good tutorial out there somewhere about decorators? Google 
> doesn't bring up much.
> 


Define "good" :)

I'm interested in what you think about this article:

http://www.artima.com/weblogs/viewpost.jsp?thread=240808

Personally, I think it's filled with jargon that will be alien to most 
Python coders, but otherwise interesting.

Here's my cheap introduction to decorators...

Before you understand decorators, you have to understand two things 
about Python:

(1) Functions are "first class objects";

(2) and therefore you can write "factory functions".

Everything else is just a bonus.

What I mean by "first class" is best explained by giving an example of 
the opposite, second class objects. In some languages, functions are 
"special", and by special I mean they are more restricted. You cannot 
(easily, or at all) pass a function as an argument to another function, 
or give it a new name. This makes a lot of code hard to write. For 
instance, you might want to create a Grapher application, that lets the 
user draw the graph of some function. The basic algorithm would be 
something like this:

def grapher(function, low, high, step):
     for x in range(low, high, step):
         y = function(x)
         draw_pixel(x, y)  # draw a pixel on the graph, somehow...

This is easy in Python, but very hard in languages where functions are 
second class. Because they are second class, you cannot pass them as 
arguments:

grapher(math.sin, 0, 100, 1)

is not allowed in some languages, but is allowed in Python.

One consequence of this is the idea of "factory functions" is allowed. 
You can write a function which builds new functions, and returns them:

 >>> def factory(x):
...     def inner(arg):
...         return arg + x
...     return inner  # return the function object itself
...
 >>> plusone = factory(1)
 >>> plustwo = factory(2)
 >>>
 >>> plusone(23)
24


Decorators are a special type of factory function. They take as their 
argument a function, and then modify, wrap, or replace the function to 
perform special processing. Because the decorator itself is a function, 
anything you can do in Python, you can do in a decorator. The only 
limitation is that it must take a single argument, expected to be a 
function. (Or a class, in newer versions of Python.) Everything else is 
up to you!

"Decorator syntax" is the special syntax you often see:

@decorator
def spam():
     pass


is syntactic sugar for the longer version:

def spam():
     pass

spam = decorator(spam)



What are decorators good for?

The most common use is to *wrap* the function so as to eliminate 
boilerplate code.

Suppose you have a bunch of functions that look like this:


def func(arg):
     if isinstance(arg, int) and arg > 0:
         arg = min(arg, 1000)
         do stuff
     else:
         raise ValueError('bad argument')

def func2(arg):
     if isinstance(arg, int) and arg > 0:
         arg = min(arg, 1000)
         do different stuff
     else:
         raise ValueError('bad argument')

def func3(arg):
     if isinstance(arg, int) and arg > 0:
         arg = min(arg, 1000)
         do something else
     else:
         raise ValueError('bad argument')

All of the functions go through the same boilerplate at the beginning 
and end, testing for a valid argument, raising an error if not, 
adjusting the argument value... Only the "do stuff" parts are different. 
This is a lot of duplicated code. We can reduce the duplication, making 
it easier to maintain and test, by moving all the common code into one 
place:


def test_argument(func):
     def inner(arg):
         if not (isinstance(arg, int) and arg > 0):
             raise ValueError('bad argument')
         arg = min(arg, 1000)
         return func(arg)
     return inner


@test_argument
def func(arg):
     do stuff

@test_argument
def func2(arg):
     do different stuff

@test_argument
def func3(arg):
     do something else again


The common code (the boilerplate) is now inside the decorator. The 
decorator takes a function as an argument, wraps it with an inner 
function that calls the common boilerplate code, tests the argument, 
raises an error if needed, and returns the result of calling the unique 
"do stuff" part.


-- 
Steven


More information about the Tutor mailing list