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.
timertakes one argument:function(this will be the function you're decorating)Inside it, there's a
wrapperfunction definedWait ! , you might wonder what are these
*argsand**kwargsarguments*argsmeans: accept any number of positional arguments as a tuple**kwargsmeans: 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
*argsand**kwargsallow 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 + bWhen 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
exampleFunctionto thetimer()function asfunctiontimer()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 :
