Can I combine two decorators into a single one in Python?
A bit more general:
def composed(*decs):
def deco(f):
for dec in reversed(decs):
f = dec(f)
return f
return deco
Then
@composed(dec1, dec2)
def some(f):
pass
is equivalent to
@dec1
@dec2
def some(f):
pass
If the decorators don't take additional arguments, you could use
def compose(f, g):
return lambda x: f(g(x))
combined_decorator = compose(decorator1, decorator2)
Now
@combined_decorator
def f():
pass
will be equivalent to
@decorator1
@decorator2
def f():
pass
Yes. See the definition of a decorator, here.
Something like this should work:
def multiple_decorators(func):
return decorator1(decorator2(func))
@multiple_decorators
def foo(): pass
Decorators are just functions that take a function as input and return a new function. This:
@deco
def foo():
...
Is equivalent to this:
def foo():
...
foo = deco(foo)
In other words, the decorated function (foo
) is passed as an argument to the decorator, and then foo
is replaced with the return value of the decorator. Equipped with this knowledge, it's easy to write a decorator that combines two other decorators:
def merged_decorator(func):
return decorator2(decorator1(func))
# now both of these function definitions are equivalent:
@decorator2
@decorator1
def foo():
...
@merged_decorator
def foo():
...
It gets a little trickier if the decorators accept arguments, like these two:
@deco_with_args2(bar='bar')
@deco_with_args1('baz')
def foo():
...
You might wonder how these decorators are even implemented. It's actually pretty simple: deco_with_args1
and deco_with_args2
are functions that return another function decorator. Decorators with arguments are essentially decorator factories. The equivalent of this:
@deco_with_args('baz')
def foo():
...
Is this:
def foo():
...
real_decorator = deco_with_args('baz')
foo = real_decorator(foo)
In order to make a decorator that accepts arguments and then applies two other decorators, we have to implement our own decorator factory:
def merged_decorator_with_args(bar, baz):
# pass the arguments to the decorator factories and
# obtain the actual decorators
deco2 = deco_with_args2(bar=bar)
deco1 = deco_with_args1(baz)
# create a function decorator that applies the two
# decorators we just created
def real_decorator(func):
return deco2(deco1(func))
return real_decorator
This decorator can then be used like this:
@merged_decorator_with_args('bar', 'baz')
def foo():
...