Decorators in Python 2026
Decorators are one of Python’s most powerful and elegant features. A decorator is a function that takes another function as input, adds some behavior to it, and returns a new function. In 2026, decorators are widely used for logging, authentication, caching, timing, validation, and more.
TL;DR — Key Takeaways 2026
- A decorator is a function that wraps another function to extend its behavior
- Use the
@decorator_namesyntax for clean application - Always use
functools.wrapsto preserve original function metadata - Decorators can accept parameters (decorator factories)
1. Basic Decorator
from functools import wraps
def timer(func):
"""Decorator that measures execution time."""
@wraps(func)
def wrapper(*args, **kwargs):
import time
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f"{func.__name__} took {end - start:.4f} seconds")
return result
return wrapper
@timer
def slow_operation(n: int):
"""Simulate a slow task."""
import time
time.sleep(0.1)
return n * n
slow_operation(10)
2. Decorator with Parameters (Decorator Factory)
def repeat(times: int):
"""Decorator factory that repeats a function multiple times."""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet(name: str):
print(f"Hello, {name}!")
greet("Alice")
3. Best Practices for Decorators in 2026
- Always use
@wraps(func)fromfunctoolsto preserve__name__,__doc__, and signature - Keep decorators simple and focused on a single responsibility
- Use decorator factories when you need to pass parameters
- Document what the decorator does and any side effects
- Consider using libraries like
wraptfor advanced decorator needs
What a Decorator Really Does
The @decorator line above a function is shorthand. Writing @timer above def load(): is the same as writing load = timer(load) after the definition. The decorator receives the function and returns a new callable that replaces it.
Most decorators return a wrapper function that does something before or after calling the original, such as timing, logging, caching or checking arguments. The wrapper should accept *args and **kwargs so it works with any signature.
functools.wraps copies the original name, docstring and other metadata to the wrapper. Without it, every decorated function would be called wrapper in tracebacks and help().
The standard library already offers useful decorators, such as functools.lru_cache, functools.cache and dataclasses.dataclass.
Worked Example: A Counting Decorator With wraps
We write a decorator that counts calls and show what wraps preserves.
import functools
def count_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
wrapper.calls += 1
return func(*args, **kwargs)
wrapper.calls = 0
return wrapper
@count_calls
def square(n):
"""Return n squared."""
return n * n
print([square(i) for i in range(4)])
print(square.calls, square.__name__, square.__doc__)
@functools.lru_cache(maxsize=None)
def fib(n):
return n if n < 2 else fib(n - 1) + fib(n - 2)
print(fib(30), fib.cache_info().hits)
The decorated square returns [0, 1, 4, 9] and reports 4 calls, and wraps keeps the name square and its docstring. The cached fib(30) returns 832040 and the cache served 28 repeated calls.
Practical Tips
Keep decorators focused on one concern, such as timing, logging or caching. Stacking several small decorators is clearer than one decorator that does everything, and each one can be tested separately.
Remember that stacked decorators apply from the bottom up. The decorator nearest the def wraps the function first, and the top one wraps the result.
When a decorator must keep state, such as a call count, attach it to the wrapper as in the example or use a class with __call__.
lru_cache only works with hashable arguments, so pass tuples rather than lists to cached functions.
Apply decorators from the standard library first, such as functools.cache, before writing your own. They are well tested, fast and familiar to other Python developers.
Decorated functions should still be easy to test, so keep the original logic separate where needed.
Common Mistakes
- Forgetting to return the wrapper from the decorator.
- Leaving out functools.wraps.
- Writing wrappers that accept only fixed arguments.
- Re-implementing caching instead of using lru_cache.
Quick Summary
- @dec is shorthand for f = dec(f).
- Wrappers use
*argsand**kwargs. - functools.wraps keeps metadata.
- Use standard decorators when they fit.
Conclusion
Decorators are a cornerstone of modern Python development. In 2026, mastering decorators allows you to write clean, reusable, and powerful code by separating cross-cutting concerns from your core business logic. From simple timing and logging to complex authentication and caching, decorators remain one of Python’s most beloved features.
Next steps:
- Review your codebase and identify repeated behavior that can be replaced with decorators