🗃 dek
- the decorator-decorator 🗃
dek
decorates your decorators to diminish defects and drudgery.
Writing a Python decorator which takes no parameters isn't hard.
But writing a decorator with parameters is less easy - and more work
if you want to decorate classes, like unittest.mock.patch
does.
dek
is a decorator for decorators that does this deftly with a
single tiny function.
TASK: write a decorator before
that prints a function's name and its
arguments before it executes.
With dek
, it's a few lines:
import dek
@dek
def before(pfunc):
print(pfunc)
return pfunc()
Done! To use your new decorator:
@before
def phone(two, four=4):
print('Calling', two + two, four * four)
one(32, four=3)
# That prints something like:
#
# functools.partial(<function phone at 0x7fafa8072b00>, 32, four=3)
# Calling 64 9
pfunc
is a functools.partial
,
which represents the function call that dek
intercepted. Your code
can call pfunc
as often as you like, or add or change parameters.
import functools
def before(func):
@functools.wraps(func)
def wrapped(*args, **kwargs):
print(func, args, kwargs)
return func(*args, **kwargs)
return wrapped
With dek
it's a bit less work, but the real advantage comes when you have
a decorator with a parameter.
Write a decorator before
that prints a function's name, arguments
and a label before it executes.
With dek
, it's a trivial change from the previous solution.
import dek
@dek
def before(pfunc, label='dull'):
print(label, pfunc.func, *pfunc.args)
return pfunc()
@before
def add(x, y):
return x + y
@before(label='Exciting!')
def times(x, y):
return x * y
print('Result', add(2, times(2, 3)))
# Prints:
# Exciting! times 2 3
# dull add 2 6
# Result 8
Without dek
it's actual work that's easy to get wrong.
import functools
def before(func=None, label='dull'):
if func is not None:
@functools.wraps(func)
def wrapped(*args, **kwargs):
print(label, func.__name, *args)
return func(*args, **kwargs)
return wrapped
return functools.partial(before, label=label)
For finer control over function signatures there is deferred mode, which
lets you select what sort of signature you want to expose with a wrapped
function that you create.
@dek(defer=True)
def before(func, label='debug'):
def wrapped(foo, bar):
print(label, foo, bar)
return func(foo, bar)
return wrapped
If you need to decorate methods on a class, there's a methods
parameter to
select which methods get decorated.
import dek
@dek(methods='test')
def before(pfunc):
print('HERE', *pfunc.args)
return pfunc()
@before
class Class:
def test_one(self):
return 1
def test_two(self):
return 2
def three(self): # This won't get decorated
return 1
# Test at the command line:
>>> cl = Class()
>>> cl.test_one(), cl.test_two(), cl.three()
HERE 1
HERE 2
(1, 2, 3)
NOTES:
This article talks more about
decorators that take parameters and about dek
in general.
For your advanced decorator desires, the PyPi module
decorator
does not duplicate duties that dek
does, but does
pretty anything else you could conceive of in a decorator library.