views:

192

answers:

5

Trying to find examples of when decorators might be really beneficial, and when not so much. Sample code is appreciated.

+4  A: 

The usual example is using the @property decorator to make a read-only property:

@property
def count(self):
    return self._events

instead of:

def _get_count(self):
    return self._events

count = property(_get_count)
Mike DeSimone
+1  A: 

In the AppEngine API, there's the nice @login_required decorator, which can clean up code quite a bit:

class MyPage(webapp.RequestHandler):
    @login_required
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.out.write("Hello, world!")

As opposed to:

class MyPage(webapp.RequestHandler):
    def get(self):
        user = users.get_current_user()
        if not user:
            return self.redirect(users.create_login_url(self.request.uri))

        self.response.headers['Content-Type'] = 'text/plain'
        self.response.out.write("Hello, world!")

The other ones I find myself using most are @classmethod for class methods, @staticmethod for static methods, and (as Mike DeSimone said) @property for read-only properties. It just reads nicer to have the decorator before the function rather than after it, like in

class Bar(object):
    @classmethod
    def foo(cls):
        return id(cls)

instead of:

class Bar(object):
    def foo(cls):
        return id(cls)
    foo = classmethod(foo)

It just saves boilerplate code.

meeselet
+8  A: 

Decorators are simple syntax for a specific way to call higher-order functions, so if you're focusing just on the syntax it's unlikely to make a great difference. IOW, wherever you can say

@mydecorator
def f(...):
  # body of f

you could identically say

def f(...):
  # body of f
f = mydecorator(f)

The decorator syntax's advantage is that it's a wee bit more concise (no repeating f three times;-) and that it comes before the def (or class, for class decorators) statement, thus immediately alerting the reader of the code. It's important, but it just can't be great!

The semantics of decorators (and, identically, of higher-order function calls that match this pattern, if there were no decorators;-). For example,

@classmethod
def f(cls, ...):

lets you make class methods (very useful esp. for alternate constructors), and

@property
def foo(self, ...):

lets you make read-only properties (with other related decorators in 2.6 for non-read-only properties;-), which are extremely useful even when not used (since they save you from writing lot of dumb "boilerplate" accessors for what are essentially attributes... just because access to the attribute might require triggering some computation in the future!-).

Beyond the ones built into Python, your own decorators can be just as important -- depending on what your application is, of course. In general, they make it easy to refactor some part of the code (which would otherwise have to be duplicated in many functions and classes [[or you might have to resort to metaclasses for the class case, but those are richer and more complicated to use correctly]]) into the decorator. Therefore, they help you avoid repetitious, boilerplatey code -- and since DRY, "Don't Repeat Yourself", is a core principle of software development, any help you can get towards it should be heartily welcome.

Alex Martelli
+4  A: 

The easiest way to understand the usefulness of decorators is to see some examples. Here is one, for instance:

Suppose you are studying some code and wish to understand when and how a function is called. You can use a decorator to alter the function so it prints some debugging information each time the function is called:

import functools
def trace(f):
    '''This decorator shows how the function was called'''
    @functools.wraps(f)
    def wrapper(*arg,**kw):            
        arg_str=','.join(['%r'%a for a in arg]+['%s=%s'%(key,kw[key]) for key in kw])
        print "%s(%s)" % (f.__name__, arg_str)
        return f(*arg, **kw)
    return wrapper

@trace
def foo(*args):
    pass


for n in range(3):
    foo(n)

prints:

# foo(0)
# foo(1)
# foo(2)

If you only wished to trace one function foo, you could of course add the code more simply to the definition of foo:

def foo(*args):
    print('foo({0})'.format(args))

but if you had many functions that you wished to trace, or did not want to mess with the original code, then the decorator becomes useful.

For other examples of useful decorators, see the decorator library.

unutbu
Your docstring should probably be applied to `trace`, where you can look it up, rather than `wrapper`, where it is destroyed by `wraps` anyhow.
Mike Graham
@Mike: Ah yes. Thanks.
unutbu
A: 

Decorators are for design choices where you are merging two concepts, like "Logging" and "Inventory Management" or "is registered user" and "View Lastest Messages".

  • One of those concepts is wrapping the other, controlling how it is called. That concept is the decorator.
  • The second concept is permanently joined to the first, so much so that it is OK to lose the ability to call the second concept directly. For example, losing the ability to call "View Latest Messages" without also calling "Is registered user"

When the design choice is correct, the decorator syntax (or syntactic sugar for decorators) reads cleanly and moves to eliminate errors from misunderstanding.

The usual decorator concepts include:

  • Logging. This might be joined with a transaction processor to log each successful or unsuccessful transaction.
  • Requiring security. This might be coupled with changing price in an inventory.
  • Caching (or memoizing). This might be coupled with Net Present Value computations or any expensive, static, read only operation.
  • Language fix-ups like "@classmethod" or "convert error return values to exceptions"
  • Registration with frameworks, such as "this function gets called when that button is pressed.
  • state machine processing, where the decorator decides whch state to process next.
  • etc. etc.

You might look at http://wiki.python.org/moin/PythonDecoratorLibrary (a dated wiki page) or the dectools (http://pypi.python.org/pypi/dectools) library for more documentation and examples.

Charles Merriam