Saturday, September 08, 2007

Maybe ... just maybe ... I'm starting to see what Python decorators are all about.

Have a look at this example :




log1 = []
log2 = []

def declogFactory(l) :
def declog(f) :
def new(*args, **kws) :
l.append((f.__name__,args[0],args[1]))
return f(*args, **kws)
return new
return declog

declog = declogFactory(log1)

@declog
def add(x,y) :
return x + y

@declog
def times(x,y) :
return x * y

print add(1,2)
print log1, log2
print times(4,5)
print log1, log2




log1 and log2 are lists representing two alternative logs

declogFactory is actually a routine which is going to return a closure which is a python decorator.


so declogFactory receives a list as an argument and returns a decorator with the variable l bound to that list.

we assign that decorator to "declog" in the main program.

but what is declog?

decorators are functions which transform other functions, and are invoked with the @ syntax before the function they transform.

So this :



@declog
def add(x,y) :
return x + y



is applying the new decorator "declog" to the function "add".

Let's look inside the definition of the decorator



def declog(f) :
def new(*args, **kws) :
l.append((f.__name__,args[0],args[1]))
return f(*args, **kws)
return new



this decorator also takes one argument : f that will be the function to be decorated.

we, in turn define function new. All it does is append the name of the function f it receives, with the first two arguments, to the log. Then it calls f.

effectively we've wrapped a call to f inside another routine which updates the log first.

however, we don't need to do this explicitly. You'll see that, although the routines add and times have been decorated, the calls to them look exactly the same as a call to a non-decorated version.

the simple occurance of the decorator line @declog before each definition is sufficient to turn each routine into a logged routine, as we see from the output


3
[('add', 1, 2)] []
20
[('add', 1, 2), ('times', 4, 5)] []


both add and times are decorated.

note, as well, that the decorator was itself parameterized with which log. it's sufficient to change the line defining the decorator to


declog = declogFactory(log2)


to have the logging from all the functions redirected to log2 instead of log1

No comments: