You could do something like this by incorporating @warvariuc's answer to the question Decorator that prints function call details (parameters names and effective values)? into your own decorator thusly:
import inspect
import time
def warn_slow(func):
'''Decorator that warns when a call to the function takes too long.'''
TIME_LIMIT = 2
def wrapper(*args, **kwargs):
func_args = inspect.signature(func).bind(*args, **kwargs).arguments
func_args_str = ", ".join("{}={!r}".format(*item) for item in func_args.items())
start_time = time.time()
result = func(*args, **kwargs)
time_elapsed = time.time() - start_time
if time_elapsed > TIME_LIMIT:
print(f"execution time of {func.__module__}.{func.__qualname__}"
f"({func_args_str}) took more than {TIME_LIMIT} seconds")
return result
return wrapper
@warn_slow
def test_func_a(a, b, c=None):
time.sleep(1)
@warn_slow
def test_func_b(a, b, c=None):
time.sleep(2.5)
test_func_a(1, 2, c=13)
test_func_b(3, 4, c=42)
Output produced:
execution time of __main__.test_func_b(a=3, b=4, c=42) took more than 2 seconds
Generalizing
When programming, it's usual best to avoid "hard coding" constant literal values into your code as much as possible. In this case the decorator can be make more flexible by allowing the 2 second time limit to be overridden. One way of doing that requires making it accept an optional argument specifying what the limit should be. To implement that I used the generic "decorator decorator" from @Nicole's answer to a question on how to implement making them do that. I chose it partly because it required the least amount of modifications to decorator shown above (and is itself very generic).
Here's the result:
import inspect
import time
def optional_arg_decorator(fn): # From https://stackoverflow.com/a/20966822/355230
def wrapped_decorator(*args):
if len(args) == 1 and callable(args[0]):
return fn(args[0])
else:
def real_decorator(decoratee):
return fn(decoratee, *args)
return real_decorator
return wrapped_decorator
@optional_arg_decorator
def warn_slow(func, time_limit=2):
''' Decorator that warns when a call to the function takes too long.
Accepts optional argument to override default time limit.
'''
def wrapper(*args, **kwargs):
func_args = inspect.signature(func).bind(*args, **kwargs).arguments
func_args_str = ", ".join("{}={!r}".format(*item) for item in func_args.items())
start_time = time.time()
result = func(*args, **kwargs)
time_elapsed = time.time() - start_time
if time_elapsed > time_limit:
print(f"execution time of {func.__module__}.{func.__qualname__}"
f"({func_args_str}) took more than {time_limit} seconds")
return result
return wrapper
@warn_slow
def test_func_a(a, b, c=None):
time.sleep(1)
@warn_slow
def test_func_b(a, b, c=None):
time.sleep(2.5)
@warn_slow(3) # Override default time limit.
def test_func_c(a, b, c=None):
time.sleep(3.1)
test_func_a(1, 2, c=10)
test_func_b(3, 4, c=2)
test_func_c(5, 6, c=42)
Output:
execution time of __main__.test_func_b(a=3, b=4, c=2) took more than 2 seconds
execution time of __main__.test_func_c(a=5, b=6, c=42) took more than 3 seconds