[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