[Tutor] decorators -- treat me like i'm 6.. what are they.. why are they?

Steven D'Aprano steve at pearwood.info
Wed Jul 6 22:18:43 EDT 2016


On Wed, Jul 06, 2016 at 03:35:16PM -0400, bruce wrote:
> Hi.
> 
> Saw the decorator thread earlier.. didn't want to pollute it. I know, 
> I could google!
> 
> But, what are decorators, why are decorators? who decided you needed 
> them!

Sometimes you find yourself writing many functions (or methods) that 
have bits in common.

For example, you might have a bunch of mathematical functions that all 
start off with the same boring code "check that the argument x is a 
number, raise an exception if it isn't". Sure, it's only two or three 
lines, but if you've got twenty functions to write, you have to write 
those same lines twenty times. And then if you decide to change the 
error message, or change the way you check something is a number, you 
have to do that in twenty places.

A decorator let's you factor out that common code into a single place, 
so you only need to make changes to *one* place instead of twenty 
separate functions. You still have to apply the decorator to each of the 
functions, but that's only once line. Besides, a decorator is not really 
about cutting back the number of lines of code (although it is nice when 
that happens) as it is about moving common code to one place.

def verify_numeric_argument(func):
    """Decorator that checks the argument to a function is a number."""
    @functools.wraps(func)
    def inner(x):
        if isinstance(x, numbers.Number):
            return func(x)
        else:
            kind = type(x).__name__
            raise TypeError("expected a number, but got %s" % kind)
    return inner


@verify_numeric_argument
def sinc(x):
    if x == 0:
        return 1.0
    else:
        return math.sin(x)/x



Obviously you wouldn't bother if you're only going to do it *once*. 
There's a bunch of overhead involved in writing a decorator, usually 
about five or six lines of code per decorator. The rule of thumb I use 
is that if I have one or two functions with the same, common, chunk of 
code, I probably wouldn't bother; if I have three functions, I might, 
and if I have four or more, then I probably will.

But as I said, it's not really about saving lines of code. It's about 
keeping code that belongs together in one place, and about recognising 
when functions repeat the same structure. If you have a bunch of 
functions that look like this:

def function(arguments):
    boring boilerplate that is the same each time
    interesting bit that is different each time
    more boring boilingplate
    return result

then this is a great candidate for a decorator: move the boring 
boilerplate into the decorator, then turn the functions into this:

@apply_boilerplate
def function(arguments):
    interesting bit that is different each time
    return result


Doesn't that look better? That's the purpose of the decorator: move the 
boring bits, or sometimes the hard, complicated bits, away so you can 
focus on the interesting bits. Depending on just how boring that 
boilerplate is, I might even do it for ONE function, just to get it out 
of the way.


-- 
Steve


More information about the Tutor mailing list