How Python Decorators Work Behind the Scenes

Let us understand what do you mean by Decorators first :
A decorator is a special kind of function in Python that:
Takes another function as input, adds extra functionality to it, and returns a new function.
It’s like a wrapper that enhances a function without modifying its code directly.
Why Use Decorators?
For logging
For timing functions
For authorization checks
For caching results
To make code clean, reusable, and DRY (Don't Repeat Yourself)
Let us understand through an example
Write a decorator that measures the time function takes to execute
Step 1: Read this line
import time
Interpreter loads the built-in time
module so we can use time.time()
and time.sleep()
later.
Step 2: Define the timer()
function
def timer(function):
def wrapper(*args, **kwargs):
start = time.time()
result = function(*args, **kwargs)
end = time.time()
print(f'Time taken: {end - start}')
print(f'Result: {result}')
return result
return wrapper
Interpreter does not execute this now, it just stores timer
as a function object in memory.
timer
takes one argument:function
(this will be the function you're decorating)Inside it, there's a
wrapper
function definedWait ! , you might wonder what are these
*args
and**kwargs
arguments*args
means: accept any number of positional arguments as a tuple**kwargs
means: accept any number of keyword arguments as a dictionaryWe use them because a decorator might be applied to any function — and we don’t always know how many parameters that function takes!
So
*args
and**kwargs
allow us to forward any combination of arguments to the original function being decorated.Example :
Let’s say you decorate this:
@timer def add(a, b): return a + b
When you call:
add(2, 3)
Here’s what happens inside the decorator:
# Example wrapper(2, 3) # *args = (2, 3), **kwargs = {} function(*args, **kwargs) # function(2, 3) # let's say wrapper was wrapper(2,3,name=""Suyash") # *args = (2,3) , **kwargs = {"name"="Suyash"}
✅ Works perfectly — because we captured and passed the arguments forward.
wrapper
:Accepts any arguments
Calls the original function
Measures time
Prints duration and result
Returns the original result
Finally,
timer()
returns thiswrapper
timer
is now ready in memory.
Step 3: See the decorator
@timer
def exampleFunction(n):
time.sleep(n)
This is syntactic sugar for:
def exampleFunction(n):
time.sleep(n)
exampleFunction = timer(exampleFunction)
Now let’s walk through what happens here:
Python defines
exampleFunction(n)
as usual (just stores the function in memory).Then immediately calls:
exampleFunction = timer(exampleFunction)
Passes the
exampleFunction
to thetimer()
function asfunction
timer()
returnswrapper
, which now becomes the new value ofexampleFunction
exampleFunction
is no longer the original function , it is now wrapper
.
Step 4: Call the function
exampleFunction(2)
But remember:
exampleFunction = wrapper
So this is really calling:
wrapper(2)
Now here’s how it executes:
Inside wrapper(2)
:
start = time.time()
Records the current time in seconds (start
)
result = function(*args, **kwargs)
function
here is the original exampleFunction
, which calls:
time.sleep(2)
So it pauses for 2 seconds
end = time.time()
Records the time after the sleep
print(f'Time taken: {end - start}')
Prints something like:
Time taken: 2.0021
print(f'Result: {result}')
Since the original exampleFunction
doesn’t return anything, result = None
.
So it prints:
Result: None
return result
Returns None
back to the caller.
Final Output:
Time taken: 2.0021
Result: None
Summary of Interpreter Actions
Line | Interpreter Action |
import time | Loads the time module |
def timer | Stores timer in memory |
@timer | Replaces exampleFunction with wrapper |
exampleFunction(2) | Actually calls wrapper(2) |
wrapper | Runs timing logic, calls original function, prints duration & result |
Flow Diagram for Better Understanding :
Subscribe to my newsletter
Read articles from Suyash Kamath directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
