functools.wraps preserves the original function metadata when creating decorators.
1import functools23# Without wraps — metadata lost4def bad_decorator(func):5 def wrapper(*args, **kwargs):6 return func(*args, **kwargs)7 return wrapper89# With wraps — metadata preserved10@functools.wraps(func)11def good_decorator(func):12 def wrapper(*args, **kwargs):13 return func(*args, **kwargs)14 return wrapper1516@bad_decorator17def my_func():18 """My docstring."""19 pass2021print(my_func.__name__) # "wrapper" (wrong!)22print(my_func.__doc__) # None (lost!)2324@good_decorator25def my_func():26 """My docstring."""27 pass2829print(my_func.__name__) # "my_func" (correct!)30print(my_func.__doc__) # "My docstring." (preserved!)
Preserved attributes:
__name__ — function name.__doc__ — docstring.__module__ — module name.__wrapped__ — original function.Always use @functools.wraps(func) in decorators.