Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between decorator classes and decorator functions

I guess that's how they are called, but I will give examples just in case.

Decorator class:

class decorator(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print 'something'
        self.func(*args, **kwargs)

Decorator function:

def decorator(func):
    def wrapper(*args, **kwargs):
        print 'something'
        return func(*args, **kwargs)
    return wrapper

Is using one or the other just a matter of taste? Is there any practical difference?

like image 391
Juliusz Gonera Avatar asked Jan 10 '11 18:01

Juliusz Gonera


3 Answers

If you can write a function to implement your decorator you should prefer it. But not all decorators can easily be written as a function - for example when you want to store some internal state.

class counted(object):
    """ counts how often a function is called """
    def __init__(self, func):
        self.func = func
        self.counter = 0

    def __call__(self, *args, **kwargs):
        self.counter += 1
        return self.func(*args, **kwargs)


@counted
def something():
    pass

something()
print something.counter

I've seen people (including myself) go through ridiculous efforts to write decorators only with functions. I still have no idea why, the overhead of a class is usually totally negligible.

like image 124
Jochen Ritzel Avatar answered Nov 19 '22 19:11

Jochen Ritzel


It is generally just a matter of taste. Most Python programs use duck typing and don't really care whether the thing they're calling is a function or an instance of some other type, so long as it is callable. And anything with a __call__() method is callable.

There are a few advantages to using function-style decorators:

  • Much cleaner when your decorator doesn't return a wrapper function (i.e., it returns the original function after doing something to it, such as setting an attribute).

  • No need to explicitly save the reference to the original function, as this is done by the closure.

  • Most of the tools that help you make decorators, such as functools.wraps() or Michele Simionato's signature-preserving decorator module, work with function-style decorators.

  • There may be some programs out there somewhere which don't use duck typing, but actually expect a function type, so returning a function to replace a function is theoretically "safer."

For these reasons, I use function-style decorators most of the time. As a counterexample, however, here is a recent instance in which the class-style decorator was more natural for me.

like image 38
kindall Avatar answered Nov 19 '22 17:11

kindall


The proposed class decorator implementation has a slight difference with the function implementation : it will fail on methods

class Decorator(object):
def __init__(self, func):
    self.func = func

def __call__(self, *args, **kwargs):
    print('something')
    self.func(*args, **kwargs)

class A:
    @Decorator
    def mymethod(self):
        print("method")

A().mymethod()

will raise TypeError: mymethod() missing 1 required positional argument: 'self'

To add support of methods, you need to implement the __get__

import types
class Decorator2(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print('something')
        self.func(*args, **kwargs)

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return types.MethodType(self, instance)

class B:

    @Decorator2
    def mymethod(self):
        print("method")

B().mymethod()

will output

class B:...

something
method

The reason it works is that when you access B().mymethod, the __get__ is called first and supplies the bound method. Then __call__ is called

To conclude, provided you define the __get__, class and function implementation can be used the same way. See python cookbook recipe 9.9 for more information.

like image 1
Gabriel Avatar answered Nov 19 '22 19:11

Gabriel