Skip to content

Decorators

Decorators allow you to modify or extend the behavior of a function or class without changing its original code.

They are widely used in real-world applications such as:

  • Logging
  • Authentication
  • Caching
  • Validation
  • Performance tracking

Think of a decorator like a wrapper around a function.

graph TD A[Call Function] --> B[Decorator Wrapper] B --> C[Run Code Before] C --> D[Original Function] D --> E[Run Code After] E --> F[Return Result]

A decorator is just:

  • A function
  • That takes another function
  • Returns a new function
def decorator(func):
def wrapper():
# before
func()
# after
return wrapper

import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("Before function runs")
result = func(*args, **kwargs)
print("After function runs")
return result
return wrapper
@my_decorator
def greet():
print("Hello!")
greet()
greet = my_decorator(greet)


Without wraps, important details are lost:

  • Function name
  • Docstring
  • Help info
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@my_decorator
def greet():
"""This function greets the user"""
print("Hello")
print(greet.__name__) # greet
print(greet.__doc__) # This function greets the user


Decorators should work with any function signature.

That is why we use:

*args # positional arguments
**kwargs # keyword arguments
import functools
def log_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
result = func(*args, **kwargs)
print(f"{func.__name__} finished")
return result
return wrapper
@log_calls
def add(a, b):
return a + b
print(add(2, 3))

Sometimes you want to configure a decorator.

For that, you need one more layer.

def decorator_with_args(config):
def decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
return decorator

import functools
def repeat(times):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
result = None
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello {name}")
greet("Asha")


Decorators also work on classes.

They receive the class and can modify it.

def add_status(cls):
cls.status = "ready"
return cls
@add_status
class Task:
pass
print(Task.status) # ready

Common uses:

  • Logging function calls
  • Checking permissions
  • Measuring execution time
  • Caching results
  • Validating inputs

import functools
import time
def time_it(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end - start:.4f} seconds")
return result
return wrapper
@time_it
def slow_function():
time.sleep(1)
slow_function()