Skip to content

Decorators Intro

A decorator is a function that wraps another function (or class) to add behavior β€” logging, timing, access control, caching β€” without changing the wrapped function’s own code. The @decorator_name syntax above a function definition is shorthand for func = decorator_name(func).

def logger(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with {args}, {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result}")
return result
return wrapper
@logger
def add(a, b):
return a + b
add(2, 3)
# Calling add with (2, 3), {}
# add returned 5
# Without the @ shorthand, this is exactly what @logger does
def add(a, b):
return a + b
add = logger(add)
# Preserving the original function's metadata with functools.wraps
from functools import wraps
def logger(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@logger
def add(a, b):
"""Add two numbers."""
return a + b
print(add.__name__) # 'add' β€” without @wraps this would print 'wrapper'
# Decorators with their own arguments need an extra layer
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(times=3)
def greet():
print("Hi!")
greet() # prints "Hi!" three times

Forgetting @wraps(func) β€” the wrapped function silently loses its __name__, __doc__, and signature, which breaks introspection, help text, and some testing/debugging tools.

def logger(func):
def wrapper(*args, **kwargs): # missing @wraps(func) here
return func(*args, **kwargs)
return wrapper
@logger
def add(a, b):
"""Add two numbers."""
return a + b
print(add.__name__) # 'wrapper' β€” wrong!
print(add.__doc__) # None β€” the docstring is gone
# Fix: decorate the inner wrapper with functools.wraps(func)
  1. What does @logger above a function definition actually do under the hood?

    AnswerIt replaces the function with `logger(func)` β€” equivalent to writing `func = logger(func)` right after the `def`.
  2. Why does a decorator’s inner function usually accept *args, **kwargs?

    AnswerSo the wrapper works for *any* wrapped function's signature, not just one with a specific set of parameters.
  3. What problem does functools.wraps solve?

    AnswerIt copies the original function's `__name__`, `__doc__`, and other metadata onto the wrapper, so decorated functions still introspect correctly.