Decorators
A decorator is a function that takes a function as input, wraps it with extra behavior, and returns a new function.
Basic Decorator
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Before")
result = func(*args, **kwargs)
print("After")
return result
return wrapper
@my_decorator
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
# Before
# Hello, Alice!
# After
@my_decorator is syntactic sugar for greet = my_decorator(greet).
Preserving Metadata with functools.wraps
Without @wraps, the wrapper hides the original function's name and docstring.
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
Decorator with Arguments
Add an extra outer layer:
def repeat(n):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(n):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def say_hi():
print("Hi!")
say_hi() # Hi! Hi! Hi!
Stacking Decorators
Applied bottom-up:
@decorator_a
@decorator_b
def func(): ...
# equivalent to: func = decorator_a(decorator_b(func))
Class Decorator
class Timer:
def __init__(self, func):
wraps(func)(self)
self.func = func
def __call__(self, *args, **kwargs):
import time
start = time.perf_counter()
result = self.func(*args, **kwargs)
print(f"{self.func.__name__} took {time.perf_counter() - start:.4f}s")
return result
Built-in Decorators
class MyClass:
class_var = 0
@staticmethod
def utility(): # no self or cls — just a namespaced function
return "static"
@classmethod
def from_string(cls, s): # receives the class, not the instance
return cls()
@property
def value(self): # call as obj.value, not obj.value()
return self._value
@value.setter
def value(self, v):
self._value = v
Common Practical Decorators
import functools, time
# Memoization / cache
@functools.lru_cache(maxsize=128)
def fib(n):
return n if n < 2 else fib(n-1) + fib(n-2)
# Retry on exception
def retry(times=3):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(times):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == times - 1:
raise
return wrapper
return decorator
Key Interview Points
- Decorators are just higher-order functions — functions that take and return functions.
- Always use
@functools.wraps(func)inside a decorator to preserve__name__,__doc__, etc. - Decorators are applied at definition time, not at call time.
@classmethodreceives the class as the first arg (cls);@staticmethodreceives nothing extra.@propertylets you expose an attribute-like interface while running logic.functools.lru_cache/functools.cacheare built-in memoization decorators.