Python decorators in Perl
October 30, 2008
I have been playing around with Python lately and this evening I stumbled upon this blog entry in which Bruce Eckel gives an excellent introduction of a Python feature called a 'decorator'. Decorators in Python are very similar to LISP macros so, of course, I got interested :)
Simply put, decorators are a way to compose functions at compile time. In the rest of this post, I will assume you know what a Python decorator is, and if you don't, just go and and read the aforementioned blog.
Bruce Eckel gives the following example of a Python decorator that wraps a function into an other function that prints a short message upon entering and exiting the original function. In Python, it looks like this:
class entryExit(object):
def __init__(self, f):
self.f = f
def __call__(self):
print "Entering", self.f.__name__
self.f()
print "Exited", self.f.__name__
@entryExit
def func1():
print "inside func1()"
@entryExit
def func2():
print "inside func2()"
func1()
func2()
Now, being a Perl addict to the bone, I couldn't help meditating on how to get a similar feature in Perl. And it's not so hard, really. Wrapping a sub within an other one can be done with a closure and some symbol table hacking. Note that I kind of ignore the issue of identifying the function's name, which is kind of not relevant to the discussion. Here is a similar implementation in Perl:
sub entryExit {
my ($f,$name) = @_;
return sub {
print "Entering $name\n";
&$f();
print "Exited $name\n";
};
}
sub func1 {
print "inside func1()\n";
}
# we can replace func1 with the closure afterwards
*{__PACKAGE__."::func1"} = entryExit(\&func1,"func1");
# or we could replace "sub func2 {" with the following:
*{__PACKAGE__."::func2"} = entryExit(sub {
print "inside func2()\n";
},"func2");
func1();
func2();
Clearly, none of those 2 methods is as smooth as the Python syntax. But with a bit of fantasy I am pretty sure we could write a Perl source filter that let us write:
@my_decorator
sub func {
# do stuff
}
and replace it with:
*{__PACKAGE__."::func"} = my_decorator(sub {
# do stuff
});
As a matter of fact, I know this can be done since I have done it partially in a prototype of a module that would have been called 'Filter::WrapSub' and can be found within the sub-contract project on sourceforge. This proof-of-concept module is basically a source filter that uses PPI to *understand* the source code of the calling file and identify the beginning and end of subroutine declarations. The big drawback of this method is that it is SLOW. really slow.
But nonetheless, it can be done. Supporting decorator arguments wouldn't be a problem either. The real limitation compared with Python is the absence of a clean object model in Perl which makes it meaningless to implement the equivalent of Python's decorator classes in Perl...
Simply put, decorators are a way to compose functions at compile time. In the rest of this post, I will assume you know what a Python decorator is, and if you don't, just go and and read the aforementioned blog.
Bruce Eckel gives the following example of a Python decorator that wraps a function into an other function that prints a short message upon entering and exiting the original function. In Python, it looks like this:
class entryExit(object):
def __init__(self, f):
self.f = f
def __call__(self):
print "Entering", self.f.__name__
self.f()
print "Exited", self.f.__name__
@entryExit
def func1():
print "inside func1()"
@entryExit
def func2():
print "inside func2()"
func1()
func2()
sub entryExit {
my ($f,$name) = @_;
return sub {
print "Entering $name\n";
&$f();
print "Exited $name\n";
};
}
sub func1 {
print "inside func1()\n";
}
# we can replace func1 with the closure afterwards
*{__PACKAGE__."::func1"} = entryExit(\&func1,"func1");
# or we could replace "sub func2 {" with the following:
*{__PACKAGE__."::func2"} = entryExit(sub {
print "inside func2()\n";
},"func2");
func1();
func2();
@my_decorator
sub func {
# do stuff
}
and replace it with:
*{__PACKAGE__."::func"} = my_decorator(sub {
# do stuff
});
As a matter of fact, I know this can be done since I have done it partially in a prototype of a module that would have been called 'Filter::WrapSub' and can be found within the sub-contract project on sourceforge. This proof-of-concept module is basically a source filter that uses PPI to *understand* the source code of the calling file and identify the beginning and end of subroutine declarations. The big drawback of this method is that it is SLOW. really slow.
But nonetheless, it can be done. Supporting decorator arguments wouldn't be a problem either. The real limitation compared with Python is the absence of a clean object model in Perl which makes it meaningless to implement the equivalent of Python's decorator classes in Perl...