Decorators 101: The Basics – Gift Wrapping Your Python Functions! 🎁
Imagine you’re wrapping a gift. You take a plain box, add some shiny paper, a ribbon, and maybe a cute tag—transforming it into something more special. Decorators in Python do the same thing for functions! They take an existing function, tweak it, and return an enhanced version without changing the original code.
Whether you're a beginner or an intermediate Python developer, understanding decorators can level up your coding skills. Let’s break them down in a simple, friendly way.
1. What Are Decorators?
Decorators are functions that modify other functions. They let you add extra behavior (like logging, timing, or access control) to a function without rewriting it. Think of them as reusable "wrappers" that enhance your functions.
A Simple Decorator Example
def my_decorator(func):
def wrapper():
print("Something happens before the function runs.") # Added behavior
func() # Original function runs here
print("Something happens after the function runs.") # Added behavior
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
Output:
Something happens before the function runs.
Hello!
Something happens after the function runs.
Here, @my_decorator
modifies say_hello()
by adding print statements before and after it runs.
2. Why Use Decorators?
Decorators help you:
✔ Avoid repetitive code (DRY principle—Don’t Repeat Yourself!)
✔ Separate concerns (keep logic clean and modular)
✔ Extend functions without modifying their original code
Common Use Cases
- Logging (track when a function runs)
- Timing (measure how long a function takes)
- Authorization (check permissions before running a function)
- Caching (store results to avoid recomputation)
3. How Decorators Work Under the Hood
When you write:
@my_decorator
def say_hello():
...
Python translates it to:
say_hello = my_decorator(say_hello)
This means:
my_decorator
takessay_hello
as input.- It returns a new function (
wrapper
) that includes extra steps. - Now,
say_hello
points to the wrapped version!
4. Decorators with Arguments
What if your function has arguments? Use *args
and **kwargs
to handle them:
def smart_decorator(func):
def wrapper(*args, **kwargs):
print("Decorator: Before function")
result = func(*args, **kwargs) # Passes arguments to original func
print("Decorator: After function")
return result
return wrapper
@smart_decorator
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
Output:
Decorator: Before function
Hello, Alice!
Decorator: After function
Now, the decorator works with any function, no matter its arguments!
5. Real-World Example: Timing a Function
Want to measure how long a function takes? A decorator makes this easy:
import time
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} took {end_time - start_time:.2f} seconds.")
return result
return wrapper
@timer
def slow_function():
time.sleep(2) # Simulate a slow task
slow_function()
Output:
slow_function took 2.00 seconds.
Now you can reuse @timer
on any function to track performance!
6. Chaining Multiple Decorators
You can apply multiple decorators to a single function:
def bold(func):
def wrapper():
return "<b>" + func() + "</b>"
return wrapper
def italic(func):
def wrapper():
return "<i>" + func() + "</i>"
return wrapper
@bold
@italic
def greet():
return "Hello!"
print(greet())
Output:
<b><i>Hello!</i></b>
The order matters! @bold
wraps @italic
, which wraps greet()
.
7. Fun Experiment: Try It Yourself!
Here’s a challenge:
- Create a decorator that logs a message before and after a function runs.
- Apply it to a function of your choice.
- Bonus: Modify it to work with functions that take arguments.
Example starter code:
def logger(func):
def wrapper(*args, **kwargs):
print(f"Starting {func.__name__}...")
result = func(*args, **kwargs)
print(f"Finished {func.__name__}!")
return result
return wrapper
@logger
def add(a, b):
return a + b
print(add(3, 5))
Final Thoughts
Decorators are like superhero capes for functions—they add powers without changing the core! Once you master them, you’ll see them everywhere in Python (Flask routes, @staticmethod
, @property
, etc.).
Your Turn:
- Where could you use decorators in your code?
- Can you think of a function that would benefit from logging or timing?
Drop your experiments in the comments—I’d love to see what you create! 🚀
Happy decorating! 🎀