Decorator
Decorators are a powerful feature that lets you wrap a function (or class) to modify its behavior without changing its code. Think of it like putting an outfit on a functionโit looks and behaves differently, but the core is the same.
๐ง What is a Decorator?
A decorator is just a function that takes another function as input, adds something to it, and returns a new function. Read the phylosophy about Proxy Design Pattern
โ Create a Simple Decorator
def my_decorator(func):
def wrapper():
print("Before function runs")
func()
print("After function runs")
return wrapper
โ Using a Decorator
@my_decorator
def say_hello():
print("Hello!")
say_hello()
Output:
Before function runs
Hello!
After function runs
The @my_decorator
is just syntactic sugar for:
say_hello = my_decorator(say_hello)
๐ Why Use Decorators?
- Logging
- Authentication/Authorization
- Timing
- Caching
- Retry logic
- Input/output validation
- Access control
๐ฏ Real-World Example: Timing
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
value = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end - start:.4f} seconds")
return value
return wrapper
@timer
def compute():
time.sleep(1)
compute()
๐ Decorator with functools.wraps
When you write decorators, itโs good practice to use @functools.wraps()
to preserve the name and docstring of the original function.
from functools import wraps
def log(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
โ๏ธ Decorator with Arguments
Decorators with arguments (also called parameterized decorators) are super useful when you want to customize the behavior of the decorator at runtime. ๐๏ธ
๐ Basic Structure
def decorator_with_args(arg1, arg2):
def actual_decorator(func):
def wrapper(*args, **kwargs):
print(f"Decorator args: {arg1}, {arg2}")
return func(*args, **kwargs)
return wrapper
return actual_decorator
๐ง How Decorators with Arguments Work
You're basically writing a function that returns a decorator, which then returns a wrapper function.
@decorator_with_args(args)
โ gives you a decorator
โ which wraps your original function
Usage:
@decorator_with_args("Hello", 123)
def greet(name):
print(f"Hi, {name}!")
greet("Wendwessen")
๐ก Real Example: Repeating a Function
from functools import wraps
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 greet(name):
print(f"Hello, {name}!")
greet("Alice")
Output:
Hi!
Hi!
Hi!
๐ข log_if_slow
Decorator Implementation
import time
from functools import wraps
def log_if_slow(threshold=0.5):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
duration = end - start
if duration > threshold:
print(f"[SLOW] {func.__name__} took {duration:.2f} seconds.")
return result
return wrapper
return decorator
โก๏ธExample Usage
@log_if_slow(threshold=0.5)
def slow_function():
time.sleep(1.2)
print("Done with slow task.")
@log_if_slow(threshold=0.5)
def fast_function():
time.sleep(0.2)
print("Done with fast task.")
slow_function() # Will log
fast_function() # Wonโt log
โ Example Output:
Done with slow task.
[SLOW] slow_function took 1.20 seconds.
Done with fast task.
๐งช Summary
Concept | Description |
---|---|
Decorator with args | A function that returns a decorator |
Nested functions | One function returns another, which wraps the target function |
Useful for | Configurable logging, retries, rate limiting, access control |
๐ Bonus Challenge (for you!)
- Logs to a file instead of the console
- The file should be
opened
andclosed
safely
๐ฅ Read More on Context Manager
Thanks for Stopping by! Happy learning
Connect with me on