🎀
Functions Advanced

Decorators

Functions Ko Wrap Karna
💡 Decorator ek gift-wrapping jaisa hai — original gift (function) same rehta hai andar, lekin ek extra layer (wrapping paper) add hoti hai upar se, jo kuch extra behavior deti hai (jaise pretty look, ya is case mein extra functionality) bina asli gift ko badle.

Decorator ek function hai jo doosre function ko "wrap" karta hai, uska behavior modify/extend karta hai bina uski asli definition change kiye. @decorator_name syntax function definition ke upar likha jaata hai — "syntactic sugar" hai func = decorator(func) ke liye.

Common real-world use: logging (har call log karna), timing (execution time measure karna), authentication checks, caching — koi bhi "cross-cutting concern" jo bahut functions par apply karni ho, bina har function mein repeat kiye.

def my_decorator(func):
    def wrapper(*args, **kwargs):    # *args/**kwargs se KOI bhi function wrap ho sakta hai
        print("Function chalne se pehle...")
        result = func(*args, **kwargs)
        print("Function chalne ke baad...")
        return result
    return wrapper

@my_decorator
def greet(name):
    print(f"Hello, {name}!")

greet("Aarav")
# "Function chalne se pehle..."
# "Hello, Aarav!"
# "Function chalne ke baad..."
🎀
Decorator ek gift-wrapping jaisa hai — original gift (function) same rehta hai andar, lekin ek extra layer (wrapping paper) add hoti hai upar se, jo kuch extra behavior deti hai (jaise pretty look, ya is case mein extra functionality) bina asli gift ko badle.
1 / 2
⚡ झट से Recap
  • Decorator = function ko wrap karke behavior extend karna
  • @decorator_name syntax = "syntactic sugar" for func = decorator(func)
  • Logging, timing, auth checks — cross-cutting concerns ke liye common
इस page में (2 subtopics)

Bina functools.wraps ke, decorated function apna naam/docstring kho deta hai (wrapper function ka use hone lagta hai). @functools.wraps(func) is problem ko fix karta hai — debugging aur introspection ke liye important.

import functools

def my_decorator(func):
    @functools.wraps(func)   # metadata preserve karo
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def greet(name):
    """Greets someone"""
    print(f"Hello, {name}")

print(greet.__name__)   # "greet" (functools.wraps ke bina "wrapper" hota)
print(greet.__doc__)     # "Greets someone"
💡Tip: Production-quality decorators mein functools.wraps almost hamesha use hota hai — best practice, isse skip karna debugging ko confusing bana deta hai.

Kabhi-kabhi decorator ko khud arguments chahiye hote hain (jaise @retry(times=3)) — iske liye ek extra "outer" function layer add karni padti hai, jo decorator return karta hai.

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

@repeat(times=3)
def say_hi():
    print("Hi!")

say_hi()   # "Hi!" teen baar print hoga
💡Tip: 3-level nesting (repeat → decorator → wrapper) confusing lag sakta hai shuru mein — samajhne ka tareeka: repeat(3) ek decorator RETURN karta hai, jo phir say_hi par apply hota hai.