Class decorators vs function decorators

Question:

In python there are two ways to declare decorators:

Class based

class mydecorator(object):
    def __init__(self, f):
        self.f = f

    def __call__(self, *k, **kw):
        # before f actions
        self.f(*k, **kw)
        # after f actions

Function based

def mydecorator(f):
    def decorator(*k, **kw):
        # before f actions
        f(*k, **kw)
        # after f actions

    return decorator          

Is there any difference between these declarations?
In which cases each of them should be used?

Asked By: Yossi

||

Answers:

In fact there are no ‘two ways’. There is only one way (define a callable object) or as many ways as there are in python to make a callable object (it could be a method of other object, a result of lambda expression, a ‘partial’ object, anything that is callable).

Function definition is the easiest way to make a callable object and, as the simplest one, is probably the best in most cases. Using a class gives you more possibilities to cleanly code more complicated cases (even in the simplest cases it looks quite elegant), but it is not that obvious what it does.

Answered By: Jacek Konieczny

No, there are (more than) two ways to make callable objects. One is to def a function, which is obviously callable. Another is to define a __call__ method in a class, which will make instances of it callable. And classes themselves are callable objects.

A decorator is nothing more than a callable object, which is intended to accept a function as its sole argument and return something callable. The following syntax:

@decorate
def some_function(...):
    ...

Is just a slightly nicer way of writing:

def some_function(...):
    ...

some_function = decorate(some_function)

The class-based example you give isn’t a function that takes a function and return a function, which is the bog-standard vanilla decorator, it’s a class that is initialised with a function whose instances are callable. To me, this is a little weird if you’re not actually using it as a class (Does it have other methods? Does its state change? Do you make several instances of it that have common behaviour encapsulated by the class?). But normal use of your decorated function will not tell the difference (unless it’s a particularly invasive decorator), so do whatever feels more natural to you.

Answered By: Ben

Let’s just test it!

test_class = """
class mydecorator_class(object):
    def __init__(self, f):
        self.f = f

    def __call__(self, *k, **kw):
        # before f actions
        print 'hi class'
        self.f(*k, **kw)
        print 'goodbye class'
        # after f actions

@mydecorator_class
def cls():
    print 'class'

cls()
"""

test_deco = """
def mydecorator_func(f):
    def decorator(*k, **kw):
        # before f actions
        print 'hi function'
        f(*k, **kw)
        print 'goodbye function'
        # after f actions
    return decorator

@mydecorator_func
def fun():
    print 'func'

fun()
"""

if __name__ == "__main__":
    import timeit
    r = timeit.Timer(test_class).timeit(1000)
    r2 = timeit.Timer(test_deco).timeit(1000)
    print r, r2

I’ve got results like this : 0.0499339103699 0.0824959278107

This is means that class deco 2 times faster?

Answered By: Nikolay Fominyh

If you want to keep state in the decorator you should use a class.

For example, this does not work

def mydecorator(f):
    x = 0 
    def decorator():
        x += 1 # x is a nonlocal name and cant be modified
        return f(x)
    return decorator 

There are many workarounds for this but the simplest way is to use a class

class mydecorator(object):
    def __init__(self, f):
        self.f = f
        self.x = 0

    def __call__(self, *k, **kw):
        self.x += 1
        return f(self.x)
Answered By: Jochen Ritzel

When you’re creating a callable returning another callable, the function approach is easier and cheaper. There are two main differences:

  1. The function approach works automatically with methods, while if you’re using your class approach, you’d have to read on descriptors and define a __get__ method.
  2. The class approach makes keeping state easier. You could use a closure, especially in Python 3, but a class approach is generally preferred for keeping state.

Additionally, the function approach allows you to return the original function, after modifying it or storing it.

However, a decorator can return something other than a callable or something more than a callable. With a class, you can:

  1. Add methods and properties to the decorated callable object, or implement operations on them (uh-oh).
  2. Create descriptors that act in a special way when placed in classes (e.g. classmethod, property)
  3. Use inheritance to implement similar but different decorators.

If you have any doubt, ask yourself: Do you want your decorator to return a function that acts exactly like a function should? Use a function returning a function. Do you want your decorator to return a custom object that does something more or something different to what a function does? Create a class and use it as a decorator.

Answered By: Rosh Oxymoron
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.