Python Practice Question 7: Decorator functools Wraps
Python · Test 1 · question 7 of 10
A team's logging decorator is def log_calls(fn): containing an inner def wrapper(*args, **kwargs): that logs and returns fn(*args, **kwargs), and it returns wrapper. After decorating greet with @log_calls, greet.__name__ is 'wrapper', greet.__doc__ is None, and the API documentation generator lists every decorated function as wrapper.
The team wants decorated functions to keep their name, docstring and other metadata, expose the original function as __wrapped__, and still log every call. What should they change?
Show answer and explanation
Answer:
A decorator replaces the function with whatever it returns (here, wrapper), so the name and docstring seen afterwards are wrapper's. functools.wraps(fn) is a decorator for the wrapper function that calls functools.update_wrapper: it copies __module__, __name__, __qualname__, __doc__ and the other attributes in WRAPPER_ASSIGNMENTS from fn onto wrapper, merges __dict__, and sets wrapper.__wrapped__ = fn. The *args, **kwargs signature stays, so every call is still forwarded and logged.
Option A applies wraps to the wrong function, and without the argument it needs. wraps(x) takes the function to copy *from*; used bare on log_calls, it turns log_calls into a functools.partial, and decorating greet with it then returns greet itself carrying log_calls's name, with no logging at all.
Option B does keep the metadata, but only because it throws the wrapper away, so nothing is logged.
Option C fixes __name__ only. The docstring, __qualname__, __module__ and __wrapped__ are still missing, and the requirement covers all of them.
Reference: functools.wraps