In Post #137, we laid the groundwork for understanding decorators by confirming three key concepts: functions are objects, they can be passed as arguments, and they can be returned from other functions. Now, we’re ready to assemble these pieces to build our first manual decorator.
In this post, we will walk through the step-by-step pattern of “wrapping” a function. This manual process will reveal the underlying logic of what a decorator is and how it works, before we learn the special syntax for it in our next post.
Step 1: The Original Function
Let’s start with a simple, undecorated function that we want to add some extra behavior to. Its only job is to print “Whee!”.
def say_whee():
print("Whee!")
Our goal is to run some code before and after say_whee()
runs, but without changing the code inside the say_whee()
function itself.
Step 2: Creating the Decorator Function
A decorator is a function that takes another function as input, adds some functionality to it, and returns a new (or modified) function as output. Let’s build a simple decorator that prints a message before and after the original function call.
def my_decorator(func):
# 'func' is the function we want to wrap (e.g., say_whee)
# Define a new 'wrapper' function inside the decorator
def wrapper():
# 1. Do something BEFORE the original function runs
print("Something is happening before the function is called.")
# 2. Call the original function
func()
# 3. Do something AFTER the original function runs
print("Something is happening after the function is called.")
# Return the new wrapper function
return wrapper
Let’s break down this structure:
my_decorator(func)
: Our decorator function takes one argument,func
. This will be the function object we want to wrap.wrapper()
: Insidemy_decorator
, we define a nested function. This is our “wrapper”. Its job is to contain the new behavior plus the call to the original function.func()
: Inside the wrapper, we execute the original function that was passed into the decorator.return wrapper
: The outer decorator function returns thewrapper
function object itself (remember, it returns the object without calling it).
Step 3: Manually Applying the Decorator
Now we need to connect our original function, say_whee
, to our my_decorator
. We do this by passing our original function to the decorator and then reassigning the returned wrapper function back to the original name.
# Pass the 'say_whee' function object into our decorator.
# The decorator returns the 'wrapper' function, which we then
# assign back to the name 'say_whee'.
say_whee = my_decorator(say_whee)
This is the “decoration” step. The name say_whee
no longer points to our original, simple function. It now points to the wrapper
function that my_decorator
created and returned.
Step 4: Calling the Decorated Function
Now, what happens when we call say_whee()
?
say_whee()
Because we reassigned the variable in the previous step, we are no longer calling our original function directly. We are actually calling the wrapper
function. The wrapper
function then runs, and its code does the following:
- Prints the “before” message.
- Calls the original function,
func
(which it remembers from its enclosing scope), which prints “Whee!”. - Prints the “after” message.
The output is:
Something is happening before the function is called.
Whee!
Something is happening after the function is called.
What’s Next?
This pattern is the essence of a decorator: a function that takes another function, wraps it in a new function to add functionality, and returns that new wrapper function. The reassignment step say_whee = my_decorator(say_whee)
is the key to making it work seamlessly.
This manual process is a bit verbose. Python, being a language that values elegance and readability, provides a special, clean syntax for this exact pattern. In Post #139, we will learn how to do all of this in one simple step using Python’s @
syntax.
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