Attribute Handlers in Perl are like decorators in Python

examples/attributes/wrap_function.pl

use strict;
use warnings;
use 5.010;

use Attribute::Handlers;

sub Wrap : ATTR(CODE) {
    my ($package, $symbol, $referent, $attr, $data, $phase, $filename, $linenumber) = @_;
    my $function_name = *{$symbol}{NAME};

    my $new = sub {
        my $params = join ', ', @_;
        print "Before $function_name($params)\n";
        my @results = $referent->(@_);
        print "After $function_name($params) resulting in: (@results)\n";
        return @results;
    };

    no warnings qw(redefine);
    *$symbol = $new;
}

sub sum :Wrap {
    my $sum = 0;
    $sum += $_ for @_;
    return $sum;
}


say sum(2, 3);
say sum(-1, 1, 7);

Output:

Before sum(2, 3)
After sum(2, 3) resulting in: (5)
5
Before sum(-1, 1, 7)
After sum(-1, 1, 7) resulting in: (7)
7

The Wrap function is marked to be a attribute handler for subroutines (CODE).

Inside we create a new anonymous subroutine and assign it to the variable $new and the replace (redefine) the original function with this new function.

Inside the new function we call the original function by

my @results = $referent->(@_);

Before that we execute some code then after the original function returned we execute some code.

Then there is a totally ordinary function called "sub" that is marked with our new "Wrap".

Now every time we call sum() our "$new" function is going to be called.

Separating Attribute to module

Probably you'd like to make your wrapper more reusable and move it out to a module:

examples/attributes/MyWrapper.pm

package MyWrapper;
use strict;
use warnings;

use Attribute::Handlers;

sub Wrap : ATTR(CODE) {
    my ($package, $symbol, $referent, $attr, $data, $phase, $filename, $linenumber) = @_;
    my $function_name = *{$symbol}{NAME};

    my $new = sub {
        my $params = join ', ', @_;
        print "Before $function_name($params)\n";
        my @results = $referent->(@_);
        print "After $function_name($params) resulting in: (@results)\n";
        return @results;
    };

    no warnings qw(redefine);
    *$symbol = $new;
}

1;

Then, as far as I could figure out you need to "inherit" from that module using base:

examples/attributes/wrap_function_from_module.pl

use strict;
use warnings;
use 5.010;
use base 'MyWrapper';

sub sum :Wrap {
    my $sum = 0;
    $sum += $_ for @_;
    return $sum;
}

say sum(2, 3);
say sum(-1, 1, 7);

perl -I. wrap_function_from_module.pl

Wrapping recursive function

Wrapping a recursive function can help us see the calls that were made to it and the values it returned.

examples/attributes/wrap_function.pl

use strict;
use warnings;
use 5.010;

use Attribute::Handlers;

sub Wrap : ATTR(CODE) {
    my ($package, $symbol, $referent, $attr, $data, $phase, $filename, $linenumber) = @_;
    my $function_name = *{$symbol}{NAME};

    my $new = sub {
        my $params = join ', ', @_;
        print "Before $function_name($params)\n";
        my @results = $referent->(@_);
        print "After $function_name($params) resulting in: (@results)\n";
        return @results;
    };

    no warnings qw(redefine);
    *$symbol = $new;
}

sub sum :Wrap {
    my $sum = 0;
    $sum += $_ for @_;
    return $sum;
}


say sum(2, 3);
say sum(-1, 1, 7);

Output:

Before fibonacci(4)
Before fibonacci(3)
Before fibonacci(2)
Before fibonacci(1)
After fibonacci(1) resulting in: (1)
Before fibonacci(0)
After fibonacci(0) resulting in: (1)
After fibonacci(2) resulting in: (2)
Before fibonacci(1)
After fibonacci(1) resulting in: (1)
After fibonacci(3) resulting in: (2)
Before fibonacci(2)
Before fibonacci(1)
After fibonacci(1) resulting in: (1)
Before fibonacci(0)
After fibonacci(0) resulting in: (1)
After fibonacci(2) resulting in: (2)
After fibonacci(4) resulting in: (2)

Wrapper to measure elapsed time

Another example is a wrapper that measures the elapsed time in the real function.

examples/attributes/MyTimer.pm

package MyTimer;
use strict;
use warnings;
use 5.010;
use Time::HiRes qw(time);

use Attribute::Handlers;

sub Timer :ATTR(CODE) {
    my ($package, $symbol, $referent, $attr, $data, $phase, $filename, $linenumber) = @_;
    my $new = sub {
        my $start = time;
        my @results = $referent->(@_);
        my $end = time;
        say "Elapsed time: " . ($end-$start);
        return @results;
    };

    no warnings qw(redefine);
    *$symbol = $new;
}

1;

This is how we use it:

examples/attributes/wrap_function_time.pl

use strict;
use warnings;
use 5.010;
use Time::HiRes qw(time);
use base 'MyTimer';

sub sum :Timer {
    my $sum = 0;
    $sum += $_ for @_;
    return $sum;
}
say sum(2, 3);

This is the output:

$ perl -I. wrap_function_time.pl

Elapsed time: 4.05311584472656e-06
5