As mentioned in the introduction, a decorator is a function that can be applied to another function to augment its behavior. The syntactic sugar is equivalent to the following: my_func = decorator(my_func)
. But what if the decorator
was instead a class? The syntax would still work, except that now my_func
gets replaced with an instance of the decorator
class. If this class implements the __call__()
magic method, then it would still be possible to use my_func
as if it was a function:
class Decorator(object):
"""Simple decorator class."""
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('Before the function call.')
res = self.func(*args, **kwargs)
print('After the function call.')
return res
@Decorator
def testfunc():
print('Inside the function.')
testfunc()
# Before the function call.
# Inside the function.
# After the function call.
Note that a function decorated with a class decorator will no longer be considered a "function" from type-checking perspective:
import types
isinstance(testfunc, types.FunctionType)
# False
type(testfunc)
# <class '__main__.Decorator'>
For decorating methods you need to define an additional __get__
-method:
from types import MethodType
class Decorator(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('Inside the decorator.')
return self.func(*args, **kwargs)
def __get__(self, instance, cls):
# Return a Method if it is called on an instance
return self if instance is None else MethodType(self, instance)
class Test(object):
@Decorator
def __init__(self):
pass
a = Test()
Inside the decorator.
Class Decorators only produce one instance for a specific function so decorating a method with a class decorator will share the same decorator between all instances of that class:
from types import MethodType
class CountCallsDecorator(object):
def __init__(self, func):
self.func = func
self.ncalls = 0 # Number of calls of this method
def __call__(self, *args, **kwargs):
self.ncalls += 1 # Increment the calls counter
return self.func(*args, **kwargs)
def __get__(self, instance, cls):
return self if instance is None else MethodType(self, instance)
class Test(object):
def __init__(self):
pass
@CountCallsDecorator
def do_something(self):
return 'something was done'
a = Test()
a.do_something()
a.do_something.ncalls # 1
b = Test()
b.do_something()
b.do_something.ncalls # 2