What Are Decorators?
A decorator is a design pattern or language feature that allows a developer to dynamically modify or enhance the behavior of functions, methods, or classes without permanently modifying their original structure.
In simple terms, a decorator is a wrapper — it intercepts calls to a function or class and can modify its behavior before or after execution.
Decorators are most commonly associated with Python, but the concept exists in many languages under different names:
- JavaScript: Decorators (proposed)
- Java / C#: Annotations / Attributes
- TypeScript: Experimental decorators (stable with config)
- Scala / Kotlin: Annotations with metaprogramming
1. Decorator Pattern in OOP
The original Decorator Design Pattern comes from the Gang of Four (GoF) design patterns.
Intent:
Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
UML View:
Component: defines an interfaceConcreteComponent: the core objectDecorator: wraps the componentConcreteDecorator: adds behavior
This pattern is widely used in GUI toolkits, logging, and stream manipulation.
2. Function Decorators in Python
Python treats functions as first-class objects, allowing them to be passed as arguments and returned from other functions — a key enabler for decorators.
Basic Example:
def my_decorator(func):
def wrapper():
print("Before function runs")
func()
print("After function runs")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
Output:
Before function runs
Hello!
After function runs
3. Syntax Explained
Using @decorator_name is shorthand for:
say_hello = my_decorator(say_hello)
This transforms the function say_hello into the wrapped version at definition time.
4. Working with Arguments
To make decorators flexible, you usually include *args and **kwargs in the wrapper:
def log_args(func):
def wrapper(*args, **kwargs):
print(f"Arguments: {args}, {kwargs}")
return func(*args, **kwargs)
return wrapper
Example:
@log_args
def multiply(x, y):
return x * y
5. Preserving Function Metadata
By default, decorators override the function’s name and docstring. Use functools.wraps() to fix this:
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
6. Decorators with Parameters
Want to customize your decorator? Use nested functions:
def repeat(n):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(n):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def greet():
print("Hi!")
7. Real-World Use Cases
| Use Case | Example |
|---|---|
| Logging | Log each function call |
| Authentication | Check credentials before allowing access |
| Caching | Store and return previously computed results |
| Rate Limiting | Limit how often a function runs |
| Validation | Ensure input types or constraints |
| Performance | Time how long a function takes |
| Retry Logic | Automatically retry on failure |
| Access Control | Admin-only or role-based permissions |
8. Class Decorators
Decorators can also be used on classes in Python:
def singleton(cls):
instances = {}
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
@singleton
class Database:
pass
9. Chaining Multiple Decorators
You can stack decorators:
@decorator_one
@decorator_two
def my_func():
pass
Execution order:
decorator_twowrapsmy_funcdecorator_onewraps the result
10. Built-in Python Decorators
| Decorator | Description |
|---|---|
@staticmethod | Marks a method as static (no self) |
@classmethod | First argument is cls, not self |
@property | Turns method into a read-only property |
@functools.lru_cache | Memoization for fast repeated calls |
@dataclasses.dataclass | Automatically adds init, repr, etc. |
11. Decorators in Other Languages
JavaScript (proposed):
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
class Person {
@readonly
name = 'John';
}
TypeScript:
Enable via tsconfig.json:
{
"experimentalDecorators": true
}
Used in Angular for @Component, @Injectable, etc.
12. Testing Decorated Functions
Be careful when testing decorated functions:
- They may hide internal logic
- Use
functools.wraps()to preserve metadata - Consider undecorated versions for unit tests
13. When Not to Use Decorators
| Concern | Risk |
|---|---|
| Debugging difficulty | Hidden execution layers |
| Overengineering | For simple one-liners, unnecessary |
| Stack depth | Deeply nested wrappers can obscure call stack |
| Performance | Wrappers can add overhead in performance-critical code |
14. Best Practices
- Use
functools.wraps()to preserve identity - Keep decorators generic and composable
- Limit side effects inside wrappers
- Add docstrings to your decorators
- Use decorators to enforce single responsibility
Summary
| Concept | Description |
|---|---|
| Decorator | A wrapper that modifies behavior of functions/classes |
| First-class function | Required to support decorators |
| Syntax | @decorator_name |
| Common Uses | Logging, access control, retries, metrics |
| Languages | Python, JS, TS, Java (annotations), C# (attributes) |
| Pattern Origin | Gang of Four’s Decorator Design Pattern |
Decorators are one of the most elegant ways to encapsulate cross-cutting concerns — reusable, readable, and powerful.
Related Keywords
- Wrapper Function
- Higher-Order Function
- First-Class Functions
- Metadata
- Annotations
- Aspect-Oriented Programming
- Proxy Pattern
- Singleton Pattern
- Reflection
- LRU Cache
- Memoization
- Monkey Patching
- Metaprogramming
- Code Injection
- Type Hints
- Function Composition
- Middleware
- Dependency Injection









