Write a timing decorator that preserves a function name
Also published in our Blogger archive.
Write a timing decorator that preserves a function name
A timing decorator often hides the name of the function it wraps because the returned callable is named wrapper. functools.wraps copies selected metadata from the wrapped function and sets __wrapped__, so tools and logs can still identify add as add.
This recipe injects clock into timed instead of reading a system clock inside the decorator. StepClock returns 10.000 before add(2, 3) and 10.125 afterward, making the measured result exactly 0.125 seconds. The decorated call returns a pair: the original integer result, 5, and the elapsed float. The assertions separately check the arithmetic, the deterministic elapsed value, the preserved __name__, and access to the undecorated function.
In production, inject time.perf_counter for a suitable monotonic elapsed-time clock; its output should not be asserted as an exact duration. This wrapper records no duration when the wrapped function raises, because its second clock call is after the call. Add try/finally and an error-reporting policy if failed calls must be measured. wraps improves metadata, but the runtime wrapper still accepts *args and **kwargs.
from functools import wraps
class StepClock:
def __init__(self, readings):
self._readings = iter(readings)
def __call__(self):
return next(self._readings)
def timed(clock):
def decorate(function):
@wraps(function)
def wrapper(*args, **kwargs):
started = clock()
result = function(*args, **kwargs)
elapsed = clock() - started
return result, elapsed
return wrapper
return decorate
@timed(StepClock([10.000, 10.125]))
def add(left, right):
return left + right
value, elapsed = add(2, 3)
assert value == 5
assert elapsed == 0.125
assert add.__name__ == "add"
assert add.__wrapped__(2, 3) == 5
print(f"{add.__name__} {value} {elapsed:.3f}")
Expected stdout:
add 5 0.125
By Batu. AI assistance was used to prepare this article.
Source: Python functools.wraps documentation.