Introduction
Sometime you need a decorator with these properties: Value:
# a simple un-adorned decorator (form-1)
@decorator
def function():
<span style="color:green" class="x-dest-value1">x</span>
{{x:=}}
...
# same decorator but with no arguments (form-2)
@decorator()
def function():
...
# and finally a decorator with parameters and arguments (form-3)
@decorator(1, 2, a=3, b=4)
def function():
...
NOTE there is one form that cannot be correctly implemented:
@decorator(callable) def function(): ...
Unfortunately this decorator form is ambiguous and cannot be handled.
Below the description how to create such a decorator (TL;DR example.py)
Use
This is the function we're going to decorate:
def function(x, y):
return x+y**2
>>> print(function(1, 2))
5
form-1
This is how to wrap around the function with decorator, let's say doubling the function result by 2:
@decorator
def function(x, y):
return x+y**2
>>> print(fn(1, 2))
10 # eg. ( 1 + 2**2 ) * 2
form-2
In this form we want decorator to retunr the function result by 3:
@decorator()
def fn(x, y):
...
>>> print(fn(1, 2))
15 # eg. ( 1 + 2**2 ) * 3
form-3
Here decorator get the scale factor from one of its argument (eg. b=4)
@decorator(1, 2, a=3, b=4)
def fn(x, y):
...
>>> print(fn(1, 2))
20 # eg. ( 1 + 2**2 ) * 4
decorator implementation
The decorator (covering all the three forms) look like:
class decorator(Decorator):
def call(self, config, function, *args, **kwargs):
if config is None:
factor = 2 # form-1 config is None
elif not config[0]:
factor = 3 # form-2 config is ((), {}) 2-tuple
else:
factor = config[1]["b"] # form-3 config is ((1, 2), { "a" : 3, "b": 4 }) 2-tuple
return function(*args, **kwargs) * factor
NOTE we use class approach more elegant and clearer
Decorator base class implementation
This is how the base class Decorator is implemented:
class Decorator:
def __init__(self, *args, **kwargs):
from functools import update_wrapper, WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES
self._function = None
self._config = None
if (not kwargs) and (len(args) == 1) and callable(args[0]):
self._function = args[0]
update_wrapper(
self,
self._function,
assigned=WRAPPER_ASSIGNMENTS,
updated=WRAPPER_UPDATES)
else:
self._config = (args, kwargs)
def __call__(self, *args, **kwargs):
if self._function:
return self.call(self._config, self._function, *args, **kwargs)
else:
obj = self.__class__(*args, **kwargs)
obj._config = self._config
return obj
def call(self, config, function, *args, **kwargs):
return function(*args, **kwargs)