We are now about to begin our journey into one of Python’s most powerful and elegant features: decorators. Decorators can seem like magic at first, but they are a logical extension of concepts we’ve already learned. To understand them, we don’t need to learn any new syntax yet. We just need to put our existing knowledge together in a new way.
This first part of our guide will lay the essential groundwork. We will revisit and reinforce the concept from Post #104 that functions in Python are “first-class citizens”—they are objects that can be passed around just like any other piece of data.
Core Concept: Everything is an Object
The most fundamental idea you need to internalize is that in Python, almost everything is an object, including functions. A function definition (e.g., def my_func(): ...
) creates a function object and assigns it to the name my_func
.
As we first saw in Post #85, this means we can refer to the function object itself, separate from the act of calling it. Because it’s an object, we can assign it to a new variable.
def greet():
"""Prints a simple greeting."""
print("Hello, world!")
# Assign the function object 'greet' to another variable
# Note: No parentheses! We are not calling the function.
say_hello = greet
# Now, 'say_hello' is another name for the 'greet' function
print(greet)
print(say_hello)
# We can call the original function using the new name
say_hello()
The output shows that both variables point to the same function object in memory.
Prerequisite 1: Passing a Function as an Argument
Because functions are objects, we can pass them as arguments to other functions. A function that accepts another function is called a higher-order function.
Let’s create a function that takes any function as an argument and adds some simple logging messages before and after calling it.
def my_simple_function():
print("... executing my_simple_function ...")
def run_with_logging(func):
"""Takes a function as an argument and adds logging."""
print("Starting function...")
func() # Here we call the function that was passed in
print("...Function finished.")
# Pass the 'my_simple_function' object to our higher-order function
run_with_logging(my_simple_function)
This pattern—a function that “wraps” another function to add extra behavior—is the conceptual heart of a decorator.
Prerequisite 2: Returning a Function from a Function
This is the final piece of the puzzle. Just as a function can take another function as an argument, it can also return a function as its result. To do this, we often use nested functions (from Post #105).
def create_greeter():
"""This function creates and returns another function."""
print("Creating a greeter function...")
def greeter():
print("Hello from the inner function!")
# We return the inner function object itself, NOT the result of calling it.
return greeter
# 1. Call the outer function. It returns the inner function object.
my_new_function = create_greeter()
print(f"The returned object is: {my_new_function}")
# 2. Now we can call the function we received.
print("Got the inner function. Now calling it...")
my_new_function()
Let’s trace the output:
Creating a greeter function...
The returned object is: <function create_greeter.<locals>.greeter at 0x...>
Got the inner function. Now calling it...
Hello from the inner function!
This demonstrates the full flow: we call create_greeter()
, which returns the greeter
function object. We store that object in my_new_function
and can then call it whenever we want.
What’s Next?
Before you can understand decorators, you must be comfortable with these three ideas: functions are objects, you can pass them as arguments, and you can return them from other functions. If these concepts feel solid, you are ready for the next step.
Now that we have reviewed the necessary building blocks, we can assemble them. In Post #138, we will use these concepts to manually create our first decorator pattern by writing a function that takes another function, defines a new “wrapper” function inside it, and returns the wrapper.
Author

Experienced Cloud & DevOps Engineer with hands-on experience in AWS, GCP, Terraform, Ansible, ELK, Docker, Git, GitLab, Python, PowerShell, Shell, and theoretical knowledge on Azure, Kubernetes & Jenkins. In my free time, I write blogs on ckdbtech.com