Mastering Python Decorators: A 5-Step Journey
What is a Decorator?
In Python, a decorator is like a little wrapper you put around a function. It allows you to execute extra code before or after the main function runs—without changing the function's original source code.
To truly master decorators, we need to understand five core Python concepts. Let's break them down step-by-step.
Step 1 Functions are Objects
In Python, functions are "first-class objects." This means they can be stored in variables and passed around just like any other piece of data.
def recipe():
print("This is my recipe")
# Calling the function normally
recipe()
# Function Object: Notice the missing parenthesis!
# This holds the code but doesn't execute it yet.
my_recipe = recipe
# Now we execute the object
my_recipe()
() is like actually starting the stove.
Step 2 Functions Inside Functions
You can define a function inside another function. We call these inner functions or nested functions.
def outer():
print("I am the outer function")
def inner():
print("I am the inner function")
inner() # Calling inner from inside outer
outer()
Remember: The scope of inner() is restricted. You cannot call it directly from outside the outer() function. This is where the wrapping behavior begins to take shape.
Step 3 Functions Returning Functions
Since functions are objects, a function can return another function object as its result.
def outside():
def inside():
print("Hello from the inside!")
return inside # Returning the function object, NOT inside()
my_func = outside() # my_func now holds the 'inside' function
my_func() # Now we execute it
Step 4 Passing Functions as Arguments
Functions can also take other functions as input. This is commonly referred to as a callback.
def greetings(func):
print("Starting the first task...")
func() # Executing the passed function object
def task():
print("Executing the main task")
greetings(task)
This pattern allows us to "wrap" the task() function with extra logic (like the first print statement) without modifying the task() function itself.
Step 5 Making Your First Decorator
Now, let's put it all together. A decorator is a function that takes another function, wraps it in an inner function, and returns that wrapper.
def my_decorator(func):
def wrapper():
print("Before the function runs: Logging in...")
func()
print("After the function runs: Logging out...")
return wrapper
@my_decorator
def main_objective():
print("Running the main objective!")
main_objective()
@my_decorator syntax is just "syntactic sugar" for: main_objective = my_decorator(main_objective)
Frequently Asked Questions
What is a decorator in Python?
A decorator is a function that wraps another function to add extra functionality (like logging or authentication) without changing the original code.
How does the @ symbol work?
The @ symbol is a shortcut. Putting @my_decorator above a function is the same as saying my_func = my_decorator(my_func).
Can a decorator take arguments?
Yes! By adding another layer of nested functions, you can create decorators that accept their own configuration arguments.
Why use Decorators?
Decorators are incredibly powerful for keeping your code DRY (Don't Repeat Yourself). Common use cases include:
- Logging: Track when functions are called.
- Timing: Measure how long a function takes to execute.
- Authentication: Check user permissions before running a task.