Class decorators vs function decorators [duplicate]
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?
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)
When you're creating a callable returning another callable, the function approach is easier and cheaper. There are two main differences:
- 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. - 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:
- Add methods and properties to the decorated callable object, or implement operations on them (uh-oh).
- Create descriptors that act in a special way when placed in classes (e.g.
classmethod
,property
) - 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.
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.
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.
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?
精彩评论