views:

100

answers:

2

Compared to decorators applied to a function, it's not easy to understand the decorators applied to a class.

@foo
class Bar(object):
    def __init__(self, x):
        self.x = x
    def spam(self):
        statements

What's the use case of decorators to a class? How to use it?

+8  A: 

It replaces the vast majority of classic good uses for custom metaclasses in a much simpler way.

Think about it this way: nothing that's directly in the class body can refer to the class object, because the class object doesn't exist until well after the body's done running (it's the metaclass's job to create the class object -- usually type's, for all classes without a custom metaclass).

But, the code in the class decorator runs after the class object is created (indeed, with the class object as an argument!) and so can perfectly well refer to that class object (and usually needs to do so).

For example, consider:

def enum(cls):
  names = getattr(cls, 'names', None)
  if names is None:
    raise TypeError('%r must have a class field `names` to be an `enum`!',
                    cls.__name__)
  for i, n in enumerate(names):
    setattr(cls, n, i)
  return cls

@enum
class Color(object):
  names = 'red green blue'.split()

and now you can refer to Color.red, Color.green, &c, rather than to 0, 1, etc. (Of course you normally would add even more functionality to make "an enum", but here I'm just showing the simple way to put such functionality addition in a class decorator, rather than needing a custom metaclass!-)

Alex Martelli
+1. This explains some of the things I've learned by trial and error in a clear fashion.
Manoj Govindan
@Manoj, glad you liked it, tx for letting me know!
Alex Martelli
This explanation and example is great, but the decorator is missing `return cls`. This confused me when I tried to actually run the code.
AndrewF
+3  A: 

One use case I can think of is if you want to wrap all the methods of a class with one function decorator. Say you have the following decorator:

def logit(f):
    def res(*args, **kwargs):
        print "Calling %s" % f.__name__
        return f(*args, **kwargs)
    return res

And the following class:

>>> class Pointless:
    def foo(self): print 'foo'
    def bar(self): print 'bar'
    def baz(self): print 'baz'

>>> p = Pointless()
>>> p.foo(); p.bar(); p.baz()
foo
bar
baz

You can decorate all the methods:

>>> class Pointless:
    @logit
    def foo(self): print 'foo'
    @logit
    def bar(self): print 'bar'
    @logit
    def baz(self): print 'baz'

>>> p = Pointless()
>>> p.foo(); p.bar(); p.baz()
Calling foo
foo
Calling bar
bar
Calling baz
baz

But that is LAME! Instead you can do this:

>>> def logall(cls):
for a in dir(cls):
    if callable(getattr(cls, a)):
        setattr(cls, a, logit(getattr(cls, a)))
return cls

>>> @logall
class Pointless:
    def foo(self): print 'foo'
    def bar(self): print 'bar'
    def baz(self): print 'baz'

>>> p = Pointless()
>>> p.foo(); p.bar(); p.baz()
Calling foo
foo
Calling bar
bar
Calling baz
baz

UPDATE: A more generic version of logall:

>>> def wrapall(method):
    def dec(cls):
        for a in dir(cls):
            if callable(getattr(cls, a)):
                setattr(cls, a, method(getattr(cls, a)))
        return cls
    return dec

>>> @wrapall(logit)
class Pointless:
        def foo(self): print 'foo'
        def bar(self): print 'bar'
        def baz(self): print 'baz'

>>> p = Pointless()
>>> p.foo(); p.bar(); p.baz()
Calling foo
foo
Calling bar
bar
Calling baz
baz
>>> 

Full disclosure: I've never had to do this and I just made this example up.

Claudiu