✨ Python Function Decorators β€” Add Superpowers to Your Functions

Introduction 🌟

A decorator in Python is a function that wraps another function to **add extra behavior** without modifying the original function’s code. Decorators use the @ syntax and are widely used in logging, authentication, performance tracking, and more.

Note

πŸ’‘ Decorators follow a powerful concept: functions can be passed as arguments and returned as values.

1. Basic Decorator Structure 🧱

basic_decorator.py

def my_decorator(func):
    def wrapper():
        print("Before function call")
        func()
        print("After function call")
    return wrapper

@my_decorator
def greet():
    print("Hello!")

greet()

βœ” The decorator adds code before and after the original function.

2. How Decorators Work Internally πŸ”

decorator_internal.py

def my_decorator(func):
    def wrapper():
        print("Before")
        func()
        print("After")
    return wrapper

def greet():
    print("Hello")

greet = my_decorator(greet)  # Manual decoration
greet()

Note

βœ” @decorator is just syntactic sugar for manual decoration.

3. Decorator with Arguments 🀝

decorator_args.py

def smart_divide(func):
    def wrapper(a, b):
        print("Dividing", a, "and", b)
        if b == 0:
            return "Error: Cannot divide by zero!"
        return func(a, b)
    return wrapper

@smart_divide
def divide(a, b):
    return a / b

print(divide(10, 2))
print(divide(10, 0))

4. Decorators Using *args and **kwargs 🎯

To handle any number of arguments, always use:

args_kwargs_decorator.py

def logger(func):
    def wrapper(*args, **kwargs):
        print("Arguments:", args, kwargs)
        return func(*args, **kwargs)
    return wrapper

@logger
def add(a, b):
    return a + b

print(add(10, 20))

5. Decorator that Returns a Value πŸ”„

decorator_return_value.py

def uppercase(func):
    def wrapper():
        result = func()
        return result.upper()
    return wrapper

@uppercase
def message():
    return "hello world"

print(message())

6. Chaining Multiple Decorators ⛓️

multiple_decorators.py

def bold(func):
    def wrapper():
        return "<b>" + func() + "</b>"
    return wrapper

def italic(func):
    def wrapper():
        return "<i>" + func() + "</i>"
    return wrapper

@bold
@italic
def text():
    return "Hello"

print(text())  # <b><i>Hello</i></b>

Note

βœ” Decorators run from bottom to top.

7. Using Decorators for Logging πŸ“

decorator_logging.py

def log(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@log
def greet(name):
    print("Hello", name)

greet("Sathish")

8. Timing a Function ⏱️

decorator_timer.py

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print("Time taken:", end - start)
        return result
    return wrapper

@timer
def slow():
    time.sleep(2)

slow()

9. Authentication Decorator πŸ”

decorator_auth.py

def require_login(func):
    def wrapper(user):
        if user != "admin":
            return "Access Denied"
        return func(user)
    return wrapper

@require_login
def dashboard(user):
    return f"Welcome {user}"

print(dashboard("guest"))
print(dashboard("admin"))

10. Using functools.wraps πŸŽ€

Without wraps, the wrapper function hides the original function's name and docstring.

wraps_example.py

from functools import wraps

def log(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("Called", func.__name__)
        return func(*args, **kwargs)
    return wrapper

@log
def greet():
    """This is the greeting function."""
    print("Hello")

print(greet.__name__)   # greet
print(greet.__doc__)    # This is the greeting function.

Note

βœ” Always use @wraps inside decorators.

11. Decorator That Accepts Parameters πŸŽ›οΈ

decorator_with_params.py

def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                func(*args, **kwargs)
        return wrapper
    return decorator

@repeat(3)
def hello():
    print("Hello!")

hello()

βœ” Decorator takes an argument β†’ returns another decorator.

12. Real-World Use Cases 🌍

Logging API Requests

api_logging.py

@log
def fetch_data(url):
    return "Data from " + url

fetch_data("https://example.com")

Restricting Access

role_based.py

def roles_allowed(role):
    def decorator(func):
        def wrapper(user_role):
            if user_role != role:
                return "Permission denied"
            return func(user_role)
        return wrapper
    return decorator

@roles_allowed("admin")
def delete_record(role):
    return "Record deleted"

print(delete_record("user"))
print(delete_record("admin"))

Memoization (Caching) πŸš€

memoization.py

def cache(func):
    memory = {}
    def wrapper(n):
        if n in memory:
            return memory[n]
        result = func(n)
        memory[n] = result
        return result
    return wrapper

@cache
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

print(fib(30))

Conclusion πŸŽ‰

>>β€œDecorators wrap functions with extra power β€” clean, elegant, and extremely useful.” ✨

You now fully understand Function Decorators in Python! Want the next topic? Try Modules, Classes & Objects (OOP), Error Handling, or Decorators with Classes. Just tell me! 😊