After my previous post about decorating decorators I was thinking about some more potential use of this technique, and the idea of applying a decorator conditionally came up. Python supports applying a decorator conditionally using an if-else expression like this:
def log_call(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"In function: {func.__name__}")
return func(*args, **kwargs)
return wrapper
@(log_call if debugging else lambda x: x)
def do_something(a, b):
return a + b
That's pretty nice, but at the same time quite limited. We apply or not apply a decorator based on a condition at the time the function being decorated is defined. But what if we want to decide whether the decorator logic applies based on a dynamic value, each time the decorated function is invoked? We can have a (meta)decorator: conditional, that we apply to another decorator when this decorator is applied, not defined. conditional creates a new decorator that traps in its closure the original decorator and a boolean function (condition_fn) that decides whether the decorator has to be applied. This new decorator receives a function and returns a new function that in each invocation checks (based on condition_fn) if the original decorator has to be applied. Less talk, more code:
def conditional(decorator, condition_fn: Callable):
"""
metadecorator: createa a new decorator that applies the original decorator only if `condition_fn` returns True.
"""
def conditional_deco(fn: Callable):
@wraps(fn)
def wrapper(*args, **kwargs):
if condition_fn():
return decorator(fn)(*args, **kwargs)
else:
return fn(*args, **kwargs)
return wrapper
return conditional_deco
@(conditional(log_call, lambda: debugging))
def do_something2(a, b):
return a + b
print(f"- debugging {debugging}")
print(do_something2(7, 3))
debugging = False
print(f"- debugging {debugging}")
print(do_something2(7, 3))
print("------------------------")
# - debugging True
# In function: do_something2
# 10
# - debugging False
# 10
No comments:
Post a Comment