Python Notes
HubHistory of Python - Complete Guide 2026Python Applications - Complete Guide 2026Python Features - Complete Guide 2026What is Python? - Complete Guide 2026Why Learn Python? - Complete Guide 2026Your First Python Program – Hello World & Beyond 2026How to Install Python on Windows, Mac & Linux – Complete Guide 2026Install VS Code for Python – Complete Setup Guide with Extensions 2026Python Environment Setup – Virtual Environments, pip & Project Structure 2026Python IDLE – Complete Guide for Beginners 2026Python CommentsPython Data TypesPython Input and OutputPython OperatorsPython SyntaxPython Strings BasicsPython Type CastingPython VariablesBreak, Continue and Pass in PythonElif Statement in PythonFor Loop in PythonIf-Else Statement in PythonIf Statement in PythonNested If in PythonPattern Programs in PythonWhile Loop in PythonDecorators in PythonFunction Arguments in PythonFunctions Basics in PythonGenerators in PythonLambda Functions in PythonRecursive Functions in PythonReturn Statement in PythonScope of Variables in PythonAbstraction in PythonClasses and Objects in PythonConstructors in PythonEncapsulation in PythonInheritance in PythonMagic Methods (Dunder Methods) in PythonMethod Overriding in PythonPolymorphism in PythonPython Comprehensions – List, Dict, Set & GeneratorPython Dictionaries – Key-Value StorePython Dictionary Methods – Complete ReferencePython List Methods – All Built-in MethodsPython Lists – Complete GuidePython Set Methods – Complete ReferencePython Sets – Unordered Unique CollectionsPython Tuples – Immutable SequencesPython Asynchronous Programming with asyncioPython Context ManagersAdvanced Python DecoratorsAdvanced Python GeneratorsPython Iterators - Advanced GuidePython Memory ManagementPython MultiprocessingPython MultithreadingCRUD Operations in Python DatabasesMySQL with Python (mysql-connector-python)ORM Introduction with SQLAlchemyPostgreSQL with Python (psycopg2)SQLite3 with PythonIntroduction to DjangoDjango Models and ORMDjango REST API with DRFDjango ViewsIntroduction to FlaskFlask RoutingFlask Templates with Jinja2Email Automation with PythonPDF Automation with PythonSelenium Web Automation with PythonTask Scheduler with PythonWeb Scraping with PythonData Analysis with PythonData Cleaning with PythonData Visualization ProjectData Visualization with MatplotlibNumPy IntroductionPandas IntroductionStatistical Visualization with SeabornClassification AlgorithmsClustering AlgorithmsIntroduction to Machine LearningMachine Learning Project: Churn PredictionModel Evaluation and SelectionRegression AlgorithmsSave pipeline (includes preprocessing + model)AI Assistant ProjectCalculator ProjectChatbot ProjectExpense Tracker ProjectFace Detection ProjectPassword Generator ProjectWeather App ProjectPython Coding Interview QuestionsPython OOP Interview Questions and AnswersPython Master CheatsheetPython Interview Questions and AnswersPython Best PracticesCommon Python Errors and SolutionsPython Debugging TechniquesPython Learning Resources
Detailed guide to Python decorators — function decorators, class decorators, stacking, functools.wraps, and real-world use cases. Learn with examples and best practices.
Decorator is a function that wraps another function — it adds extra functionality without modifying the original function's behavior. Ye Python ka ek powerful design pattern hai!
1️⃣ Decorator Banane ka Foundation
Example
# ─────────────────────────────────────
# Step 1: Functions first-class objects hain
# ─────────────────────────────────────
def hello():
print("Hello World!")
# Store a function in a variable
my_func = hello
my_func() # Hello World!
# Pass a function as an argument
def execute(func):
print("Before function call")
func()
print("After function call")
execute(hello)
Code example
# OUTPUT:
Hello World!
Before function call
Hello World!
After function callExample
# ─────────────────────────────────────
# Step 2: Function jo function return kare
# ─────────────────────────────────────
def my_decorator(func):
"""Decorator function"""
def wrapper():
print("🔔 Some work before...")
func() # Original function call
print("✅ Some work after...")
return wrapper # wrapper return karo
def say_hi():
print("Hi there! 👋")
# Manual decoration
decorated = my_decorator(say_hi)
decorated()
Example
# OUTPUT:
🔔 Kuch kaam pehle...
Hi there! 👋
✅ Kuch kaam baad mein...
2️⃣ @ Syntax — Syntactic Sugar
Example
# ─────────────────────────────────────
# @ syntax — decorator shorthand
# ─────────────────────────────────────
def my_decorator(func):
def wrapper():
print("🔔 Before")
func()
print("✅ After")
return wrapper
# ❌ Old way (manual)
# say_hi = my_decorator(say_hi)
# ✅ New way (@ syntax)
@my_decorator
def say_hi():
print("Hi there! 👋")
# Dono exactly same hain!
say_hi()
Example
# OUTPUT:
🔔 Before
Hi there! 👋
✅ After
3️⃣ Decorator with Arguments
Example
# ─────────────────────────────────────
# Use *args and **kwargs — wrap any function
# ─────────────────────────────────────
def universal_decorator(func):
"""Can wrap any function"""
def wrapper(*args, **kwargs):
print(f"▶ {func.__name__}() calling...")
result = func(*args, **kwargs)
print(f"◀ {func.__name__}() done → returned: {result}")
return result
return wrapper
@universal_decorator
def add(a, b):
return a + b
@universal_decorator
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
@universal_decorator
def square(n):
return n ** 2
# Test karo
r1 = add(3, 4)
print()
r2 = greet("Rahul")
print()
r3 = greet("Priya", greeting="Namaste")
print()
r4 = square(7)
| ◀ add() done | returned: 7 |
| ◀ greet() done | returned: Hello, Rahul! |
| ◀ greet() done | returned: Namaste, Priya! |
| ◀ square() done | returned: 49 |
4️⃣ functools.wraps — Metadata Preserve Karna
Example
# ─────────────────────────────────────
# Problem: Decorator hides the function's name
# ─────────────────────────────────────
# Without @wraps
def decorator_bad(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@decorator_bad
def my_function():
"""Original docstring"""
pass
print(f"Name: {my_function.__name__}") # wrapper ← WRONG!
print(f"Doc: {my_function.__doc__}") # None ← LOST!
Example
# ─────────────────────────────────────
# Fix: functools.wraps use karo
# ─────────────────────────────────────
from functools import wraps
def decorator_good(func):
@wraps(func) # ← Yeh metadata preserve karta hai
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@decorator_good
def my_function():
"""Original docstring — preserved hai!"""
pass
print(f"Name: {my_function.__name__}") # my_function ✅
print(f"Doc: {my_function.__doc__}") # Original docstring ✅
| Name | wrapper |
| Doc | None |
| Name | my_function |
| Doc | Original docstring — preserved hai! |
5️⃣ Real-World: Timing Decorator
Example
# ─────────────────────────────────────
# ⏱️ Timing Decorator — execution time measure karo
# ─────────────────────────────────────
import time
from functools import wraps
def timer(func):
"""Function execution time measure karta hai"""
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
elapsed = end - start
print(f"⏱️ {func.__name__}() → {elapsed:.6f} seconds")
return result
return wrapper
@timer
def slow_function():
"""Does some slow work"""
time.sleep(0.5)
return "Done!"
@timer
def find_primes(n):
"""n tak ke prime numbers dhundhta hai"""
primes = []
for num in range(2, n + 1):
is_prime = all(num % i != 0 for i in range(2, int(num**0.5)+1))
if is_prime:
primes.append(num)
return primes
@timer
def fibonacci_list(n):
"""n Fibonacci numbers generate karta hai"""
fibs = [0, 1]
for _ in range(n - 2):
fibs.append(fibs[-1] + fibs[-2])
return fibs[:n]
# Test
result1 = slow_function()
primes = find_primes(10000)
fibs = fibonacci_list(1000)
print(f"\n✅ First 10 primes: {primes[:10]}")
print(f"✅ First 8 fibs : {fibs[:8]}")
| ⏱️ slow_function() | 0.500234 seconds |
| ⏱️ find_primes() | 0.123456 seconds |
| ⏱️ fibonacci_list() | 0.002341 seconds |
| ✅ First 10 primes | [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] |
| ✅ First 8 fibs | [0, 1, 1, 2, 3, 5, 8, 13] |
6️⃣ Real-World: Logging Decorator
Example
# ─────────────────────────────────────
# 📝 Logging Decorator — function calls log karo
# ─────────────────────────────────────
from functools import wraps
from datetime import datetime
def log_calls(func):
"""Logs function calls"""
@wraps(func)
def wrapper(*args, **kwargs):
timestamp = datetime.now().strftime("%H:%M:%S")
args_str = ", ".join([str(a) for a in args])
kwargs_str = ", ".join([f"{k}={v}" for k, v in kwargs.items()])
all_args = ", ".join(filter(None, [args_str, kwargs_str]))
print(f"[{timestamp}] 📞 CALL: {func.__name__}({all_args})")
try:
result = func(*args, **kwargs)
print(f"[{timestamp}] ✅ DONE: {func.__name__} → {result}")
return result
except Exception as e:
print(f"[{timestamp}] ❌ ERROR: {func.__name__} → {type(e).__name__}: {e}")
raise
return wrapper
@log_calls
def login(username, password):
"""User login simulate karna"""
if username == "admin" and password == "secret":
return {"status": "success", "user": username}
raise ValueError("Invalid credentials")
@log_calls
def transfer_money(from_acc, to_acc, amount):
"""Money transfer simulate karna"""
if amount <= 0:
raise ValueError("Amount must be positive")
return f"₹{amount} transferred: {from_acc} → {to_acc}"
@log_calls
def get_user_data(user_id, include_email=False):
return {"id": user_id, "name": "Rahul", "email": "r@example.com" if include_email else None}
# Test
print("=" * 50)
login("admin", "secret")
print()
login("hacker", "wrong")
print()
transfer_money("ACC001", "ACC002", 5000)
print()
get_user_data(42, include_email=True)
print("=" * 50)
| [14 | 30:25] 📞 CALL: login(admin, secret) |
| [14:30:25] ✅ DONE: login | {'status': 'success', 'user': 'admin'} |
| [14 | 30:25] 📞 CALL: login(hacker, wrong) |
| [14:30:25] ❌ ERROR: login | ValueError: Invalid credentials |
| [14 | 30:25] 📞 CALL: transfer_money(ACC001, ACC002, 5000) |
| [14:30:25] ✅ DONE: transfer_money | ₹5000 transferred: ACC001 → ACC002 |
| [14 | 30:25] 📞 CALL: get_user_data(42, include_email=True) |
| [14:30:25] ✅ DONE: get_user_data | {'id': 42, 'name': 'Rahul', 'email': 'r@example.com'} |
7️⃣ Real-World: Authentication Decorator
Example
# ─────────────────────────────────────
# 🔐 Authentication Decorator
# ─────────────────────────────────────
from functools import wraps
# Current logged-in user (simulated)
current_user = {
"name": "Rahul",
"role": "user",
"logged_in": True
}
def require_login(func):
"""Login check karta hai"""
@wraps(func)
def wrapper(*args, **kwargs):
if not current_user.get("logged_in"):
print("❌ Access denied! Please login first.")
return None
return func(*args, **kwargs)
return wrapper
def require_role(*allowed_roles):
"""Role-based access control"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
if not current_user.get("logged_in"):
print("❌ Please login first.")
return None
user_role = current_user.get("role")
if user_role not in allowed_roles:
print(f"❌ Permission denied! Required: {allowed_roles}, You have: {user_role}")
return None
return func(*args, **kwargs)
return wrapper
return decorator
# Protected functions
@require_login
def view_profile():
return f"Profile: {current_user['name']}"
@require_login
@require_role("admin", "moderator")
def delete_user(user_id):
return f"User {user_id} deleted!"
@require_login
@require_role("admin")
def view_system_logs():
return "System logs: [access.log, error.log, debug.log]"
# Test as regular user
print("🧑 Logged in as: Rahul (role: user)")
print("-" * 40)
print(view_profile()) # ✅ Allowed
print(delete_user(42)) # ❌ Denied (user can't delete)
print(view_system_logs()) # ❌ Denied (only admin)
print("\n" + "─" * 40)
# Simulate admin login
current_user["role"] = "admin"
print("👑 Switched to admin role")
print("-" * 40)
print(view_profile()) # ✅
print(delete_user(42)) # ✅
print(view_system_logs()) # ✅
| 🧑 Logged in as | Rahul (role: user) |
| Profile | Rahul |
| ❌ Permission denied! Required | ('admin', 'moderator'), You have: user |
| ❌ Permission denied! Required | ('admin',), You have: user |
| Profile | Rahul |
| System logs | [access.log, error.log, debug.log] |
8️⃣ Real-World: Retry Decorator
Example
# ─────────────────────────────────────
# 🔄 Retry Decorator — For network calls
# ─────────────────────────────────────
import time
import random
from functools import wraps
def retry(max_attempts=3, delay=1.0, exceptions=(Exception,)):
"""
Failed function ko automatically retry karta hai.
Args:
max_attempts: Kitni baar try karna (default 3)
delay: Attempts ke beech wait (seconds)
exceptions: Kaunse exceptions pe retry karna
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_exception = None
for attempt in range(1, max_attempts + 1):
try:
print(f" 🔄 Attempt {attempt}/{max_attempts}...")
result = func(*args, **kwargs)
print(f" ✅ Success on attempt {attempt}")
return result
except exceptions as e:
last_exception = e
print(f" ❌ Attempt {attempt} failed: {e}")
if attempt < max_attempts:
print(f" ⏳ Waiting {delay}s before retry...")
time.sleep(delay)
print(f" 💥 All {max_attempts} attempts failed!")
raise last_exception
return wrapper
return decorator
# Simulate unstable network call
call_count = 0
@retry(max_attempts=4, delay=0.1, exceptions=(ConnectionError, TimeoutError))
def fetch_data_from_api(endpoint):
global call_count
call_count += 1
# Simulate 60% failure rate
if random.random() < 0.6:
raise ConnectionError(f"Connection failed to {endpoint}")
return {"data": "API response", "endpoint": endpoint}
# Test
random.seed(42) # Reproducible results
call_count = 0
print("📡 Calling unstable API...")
print("-" * 40)
try:
result = fetch_data_from_api("/api/users")
print(f"\n📦 Result: {result}")
except ConnectionError:
print("\n💀 API completely unreachable!")
| ❌ Attempt 1 failed | Connection failed to /api/users |
| ❌ Attempt 2 failed | Connection failed to /api/users |
| 📦 Result | {'data': 'API response', 'endpoint': '/api/users'} |
9️⃣ Real-World: Caching Decorator
Example
# ─────────────────────────────────────
# 💾 Cache Decorator — expensive calculations cache karo
# ─────────────────────────────────────
from functools import wraps
import time
def memoize(func):
"""Function results cache karta hai"""
cache = {}
@wraps(func)
def wrapper(*args):
if args in cache:
print(f" 💾 Cache HIT for {func.__name__}{args}")
return cache[args]
print(f" 🔄 Cache MISS for {func.__name__}{args} — computing...")
result = func(*args)
cache[args] = result
return result
wrapper.cache = cache # Expose the cache
wrapper.clear_cache = lambda: cache.clear()
return wrapper
@memoize
def expensive_calculation(n):
"""Slow calculation (simulated)"""
time.sleep(0.3) # Simulate processing time
return n ** 3 + 2 * n ** 2 + 5 * n + 1
# Test
print("🧮 Running calculations:")
start = time.time()
for n in [5, 3, 5, 7, 3, 5, 10, 7]:
result = expensive_calculation(n)
print(f" f({n}) = {result}")
elapsed = time.time() - start
unique = len({5, 3, 7, 10}) # 4 unique values
print(f"\n⏱️ Total time : {elapsed:.2f}s")
print(f"🔢 Unique calcs : {unique} (rest from cache)")
print(f"💾 Cache entries : {len(expensive_calculation.cache)}")
| ⏱️ Total time | 1.21s (only 4 actual calculations × 0.3s) |
| 🔢 Unique calcs | 4 (rest from cache) |
| 💾 Cache entries | 4 |
🔟 Stacking Multiple Decorators
Example
# ─────────────────────────────────────
# Multiple decorators stack karna
# Applied in Bottom → Top order
# ─────────────────────────────────────
from functools import wraps
def bold(func):
@wraps(func)
def wrapper(*args, **kwargs):
return f"**{func(*args, **kwargs)}**"
return wrapper
def italic(func):
@wraps(func)
def wrapper(*args, **kwargs):
return f"_{func(*args, **kwargs)}_"
return wrapper
def uppercase(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs).upper()
return wrapper
# Stack them — applies bottom to top
@bold # 3rd apply hoga
@italic # 2nd apply hoga
@uppercase # 1st apply hoga
def greet(name):
return f"hello, {name}"
print(greet("python"))
# Step 1: uppercase → "HELLO, PYTHON"
# Step 2: italic → "_HELLO, PYTHON_"
# Step 3: bold → "**_HELLO, PYTHON_**"
Example
# OUTPUT:
**_HELLO, PYTHON_**
Example
# ─────────────────────────────────────
# Real-world stacking: API endpoint
# ─────────────────────────────────────
def log_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"📝 LOG: Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
def require_auth(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("🔐 AUTH: Checking credentials...")
# auth logic here
return func(*args, **kwargs)
return wrapper
def validate_input(func):
@wraps(func)
def wrapper(data, *args, **kwargs):
print(f"✅ VALIDATE: Checking input...")
if not isinstance(data, dict):
raise TypeError("Data must be a dict")
return func(data, *args, **kwargs)
return wrapper
@log_calls
@require_auth
@validate_input
def create_user(data):
"""New user create karta hai"""
print(f"👤 Creating user: {data['name']}")
return {"id": 1, **data}
result = create_user({"name": "Rahul", "email": "rahul@example.com"})
print(f"Result: {result}")
| 📝 LOG | Calling create_user |
| 🔐 AUTH | Checking credentials... |
| ✅ VALIDATE | Checking input... |
| 👤 Creating user | Rahul |
| Result | {'id': 1, 'name': 'Rahul', 'email': 'rahul@example.com'} |
🏗️ Class-Based Decorator
Example
# ─────────────────────────────────────
# Creating a decorator from a class
# ─────────────────────────────────────
class CountCalls:
"""Function call count karta hai"""
def __init__(self, func):
self.func = func
self.call_count = 0
# Metadata copy karo
self.__name__ = func.__name__
self.__doc__ = func.__doc__
def __call__(self, *args, **kwargs):
self.call_count += 1
print(f" 📊 {self.func.__name__} called {self.call_count} time(s)")
return self.func(*args, **kwargs)
@CountCalls
def process_order(order_id):
return f"Order {order_id} processed"
# Test
process_order(101)
process_order(102)
process_order(103)
print(f"\nTotal calls: {process_order.call_count}")
Example
# OUTPUT:
📊 process_order called 1 time(s)
📊 process_order called 2 time(s)
📊 process_order called 3 time(s)
Total calls: 3
🏭 Decorator Factory (Arguments wala Decorator)
Example
# ─────────────────────────────────────
# Decorator Factory — customizable decorators
# ─────────────────────────────────────
from functools import wraps
def rate_limit(calls_per_second=1):
"""
Function ko rate limit karta hai.
calls_per_second: Max kitne calls per second allowed
"""
import time
min_interval = 1.0 / calls_per_second
last_call_time = [0.0] # List to allow mutation in closure
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
now = time.time()
elapsed = now - last_call_time[0]
if elapsed < min_interval:
wait_time = min_interval - elapsed
print(f" ⏳ Rate limit: waiting {wait_time:.2f}s")
time.sleep(wait_time)
last_call_time[0] = time.time()
return func(*args, **kwargs)
return wrapper
return decorator
def validate_types(**type_hints):
"""Type validation decorator factory"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# Positional args check
param_names = list(type_hints.keys())
for i, (arg, expected_type) in enumerate(zip(args, [type_hints.get(p) for p in param_names])):
if expected_type and not isinstance(arg, expected_type):
raise TypeError(f"Argument '{param_names[i]}' must be {expected_type.__name__}, got {type(arg).__name__}")
return func(*args, **kwargs)
return wrapper
return decorator
@validate_types(name=str, age=int, salary=float)
def create_employee(name, age, salary):
return {"name": name, "age": age, "salary": salary}
# Test
try:
emp = create_employee("Rahul", 28, 75000.0)
print(f"✅ Employee created: {emp}")
except TypeError as e:
print(f"❌ {e}")
try:
emp = create_employee("Priya", "twenty", 60000.0) # Age should be int
except TypeError as e:
print(f"❌ {e}")
Example
# OUTPUT:
✅ Employee created: {'name': 'Rahul', 'age': 28, 'salary': 75000.0}
❌ Argument 'age' must be int, got str
❓ Interview Questions
Q1: Decorator kya karta hai?
A Decorator is a function that wraps another function — to add extra functionality without modifying the original function.
Q2: Why do we use functools.wraps?
Without@wraps, the decorated function's__name__,__doc__and other metadata become that of thewrapperfunction.@wrapspreserves the original function's metadata.
Q3: What is the difference between a Decorator and a Decorator Factory?
python example
# Simple decorator (no args)
@timer
def func(): pass
# Decorator factory (takes args)
@retry(max_attempts=3)
def func(): passA factory decorator takes an extra outer function that accepts arguments.
Q4: How useful are decorators? Where are they used in the real world?
- Django/Flask:@login_required,@app.route()- FastAPI:@app.get(),@app.post()- Python stdlib:@property,@staticmethod,@classmethod,@lru_cache- Testing:@pytest.mark.parametrize
Q5: In what order are multiple decorators applied?
python example
@A # 3rd apply
@B # 2nd apply
@C # 1st apply
def func(): pass
# Equivalent to: func = A(B(C(func)))📋 Summary
┌─────────────────────────────────────────────────────┐
│ DECORATORS — KEY CONCEPTS │
├─────────────────────────────────────────────────────┤
│ ✅ Decorator function ko wrap karta hai │
│ ✅ @ syntax = syntactic sugar │
│ ✅ @functools.wraps se metadata preserve karo │
│ ✅ *args, **kwargs se universal wrapper banao │
│ ✅ Decorator factory se customizable decorator │
│ ✅ Multiple decorators stack ho sakte hain │
│ ✅ Class se bhi decorator bana sakte hain │
│ ✅ Real use: logging, auth, caching, retry, timing │
└─────────────────────────────────────────────────────┘
🚀 Next: For memory-efficient iteration, read Python Generators in generators.mdx!⚠️ Common Mistakes
❌ Mistake 1: @wraps bhoolna
python example
# ❌ WRONG — metadata lost ho jayega
def my_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@my_decorator
def hello():
"""Hello function"""
pass
print(hello.__name__) # "wrapper" — galat!Example
# OUTPUT:
wrapper
python example
# ✅ CORRECT — @wraps use karo
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@my_decorator
def hello():
"""Hello function"""
pass
print(hello.__name__) # "hello" — sahi!Example
# OUTPUT:
hello
🧠 Remember: Always use @wraps(func) — it's necessary for debugging and documentation!❌ Mistake 2: Decorator mein return bhoolna
python example
# ❌ WRONG — result kho jayega
def bad_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("Before...")
func(*args, **kwargs) # ← didn't return!
print("After...")
return wrapper
@bad_decorator
def add(a, b):
return a + b
result = add(3, 4)
print(f"Result: {result}") # None! 😱Example
# OUTPUT:
Before...
After...
Result: None
python example
# ✅ CORRECT — result return karo
def good_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("Before...")
result = func(*args, **kwargs) # ← result capture karo
print("After...")
return result # ← return karo!
return wrapper
@good_decorator
def add(a, b):
return a + b
result = add(3, 4)
print(f"Result: {result}") # 7 ✅Example
# OUTPUT:
Before...
After...
Result: 7
❌ Mistake 3: Decorator Factory mein parentheses bhoolna
python example
# ❌ WRONG — parentheses miss kiya
# @retry ← Error! retry() expects arguments
# def func(): pass
# ✅ CORRECT — parentheses lagao (even without args)
# @retry()
# def func(): pass⚡ If the decorator takes arguments, always use parentheses — even if default values are being used.
❌ Mistake 4: *args, **kwargs na dena
python example
# ❌ WRONG — will only work for a specific function
def limited_decorator(func):
@wraps(func)
def wrapper(): # ← No *args, **kwargs!
return func()
return wrapper
@limited_decorator
def greet(name):
return f"Hello, {name}!"
# greet("Rahul") ← TypeError: wrapper() takes 0 positional argumentsExample
# OUTPUT:
# TypeError: wrapper() takes 0 positional arguments but 1 was given
🎯 Fix: Always write wrapper(*args, **kwargs) — so it works on any function!❌ Mistake 5: Calling the decorator instead of passing its reference
python example
# ❌ WRONG — called the decorator
# @my_decorator() ← Parentheses galat! (jab tak factory na ho)
# def func(): pass
# ✅ CORRECT — decorator reference pass karo
# @my_decorator ← Simple decorator, no parentheses
# def func(): pass❌ Mistake 6: Stacking order samajhna galat
python example
# Decorators BOTTOM-UP apply hote hain!
@A # Last apply hoga (outermost)
@B # Middle
@C # Pehle apply hoga (innermost)
def func(): pass
# Matlab: func = A(B(C(func)))
# Call order: A → B → C → original_func → C → B → A💡 Remember: Applied bottom to top, but executed top to bottom!
❌ Mistake 7: Mutable default arguments in decorator closure
Example
# ❌ RISKY — shared mutable state across all decorated functions
def bad_cache(func, cache={}): # ← Shared dict!
@wraps(func)
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper
# ✅ CORRECT — define cache in closure
def good_cache(func):
cache = {} # ← Per-function cache
@wraps(func)
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper
🛡️ Define mutable defaults (dict, list) in the function body, not in parameters!
✅ Key Takeaways
- 🎯 Decorator = Function wrapper — adds extra functionality without modifying the original function
- 🔧
@syntax is just syntactic sugar —@decoratormeansfunc = decorator(func) - 📦
@functools.wraps— always use it — preserves the function's name, docstring, and metadata - 🌐 **
*args, kwargs— use them to make a universal decorator — works on any function - 🏭 Decorator Factory (decorator with arguments) has 3 levels of nesting:
factory → decorator → wrapper - 📚 Stacking order: Bottom-up apply, Top-down execute —
@A @B @C def f=A(B(C(f))) - 🏗️ Class-based decorators are best for complex state — use the
__call__method - ⚡ Real-world uses: Logging, Timing, Authentication, Caching, Retry, Rate Limiting, Input Validation
- 🐍 Python built-in decorators:
@property,@staticmethod,@classmethod,@lru_cache,@abstractmethod - 🧪 Testing tip: The
.__wrapped__attribute of a decorated function gives access to the original function (when@wrapsis used)
❓ FAQ
Q1: What is the difference between a Decorator and a Higher-Order Function?
🤔 A Higher-order function is a function that takes a function as an argument or returns one. A Decorator is a specific pattern of higher-order functions that wraps a function to add behavior.
Q2: Does a decorator change the original function's code?
❌ No! A decorator never modifies the original function's code. It only places a wrapper on top of the original function. The original remains intact.
Q3: What is the difference between @lru_cache and a custom cache decorator?
🧠@lru_cache(fromfunctools) is production-ready — it uses an LRU (Least Recently Used) strategy with amaxsizeparameter, thread-safety, and built-in cache management.
Q4: Can a decorator be applied to both a class and a function?
✅ Yes! A decorator can be applied to any callable. When applied to a class, it can modify the class (e.g., @dataclass). But decorator design for functions vs classes differs.Q5: What is the performance impact of using decorators?
⚡ Each decorator adds an extra function call — which takes microseconds. In normal use, it's negligible. But in tight loops, avoid using decorators when performance is critical.
Q6: Debugging with multiple decorators is difficult — how to solve?
🐛 Tips: - Hamesha@wrapsuse karo — correct function name dikhega - Access the original function viafunc.__wrapped__- Remove decorators one-at-a-time to test - Addprint(func.__name__)inside the wrapper for debugging
Q7: Can a decorator change the function's return type?
⚠️ Yes, a decorator can return anything! But it's bad practice if unexpected. The decorator's job is to add behavior, not change the contract.
Q8: When should you use a decorator and when not?
✅ Use when: Same logic repeats in multiple functions (DRY principle), cross-cutting concerns (logging, auth, caching), or framework-level patterns. ❌ Avoid when: Logic is only for one function, too complex nested decorators are forming, or readability is suffering.
🎯 Interview Questions
Q1: Explain the internal mechanism of decorators in Python.
Answer: A Decorator in Python is a combination of closure and first-class functions. When you write@decorator, Python internally executesfunc = decorator(func). The decorator function returns a wrapper function that stores the reference to the original function in a closure. When the decorated function is called, the wrapper actually executes and runs extra logic before/after calling the original function.
Q2: What are the consequences of not using @wraps?
Answer: Bina@wrapske: -func.__name__"wrapper" dikhayega instead of original name -func.__doc__None ya wrapper ka docstring dikhayega -help(func)galat information dega - Debugging tools (pdb, IDE) will show the wrong function name -func.__module__,func.__qualname__will also be wrong - Serialization/pickling fail ho sakta hai
Q3: Explain the 3-layer structure of a Decorator with arguments (Decorator Factory).
Answer: A Decorator factory has 3 nested functions: ``python def factory(arg): # Layer 1: Arguments accept karo def decorator(func): # Layer 2: Function accept karo @wraps(func) def wrapper(*a, **kw): # Layer 3: Call handle karo # use arg, call func return func(*a, **kw) return wrapper return decorator`@factory(arg)first callsfactory(arg)which returnsdecorator, thendecorator(func)` is applied.
Q4: When to prefer class-based decorators over function-based?
Answer: Class-based decorator prefer karo jab: - State maintain karna ho across calls (e.g., call count, last call time) - Multiple methods chahiye (e.g.,.reset(),.get_stats()) - Complex configuration store karni ho - When you need Inheritance (creating a family of decorators) Function-based is better for simple cases — less boilerplate, more readable.
Q5: Explain the execution order of decorator stacking with an example.
Answer: Decorators are applied bottom-to-top but executed top-to-bottom: ``python @log # Applied 3rd, executes 1st (outermost) @auth # Applied 2nd, executes 2nd @validate # Applied 1st, executes 3rd (innermost) def api(): pass # = log(auth(validate(api)))`Call flow:log_wrapper → auth_wrapper → validate_wrapper → api() → validate_after → auth_after → log_after`
Q6: How does the @property decorator work internally?
Answer:@propertyimplements a descriptor protocol using__get__,__set__,__delete__methods. It returns apropertyobject that converts attribute access into a method call.@name.setterand@name.deleteradd additional descriptors. Internally, it's a class-based decorator that uses Python's descriptor protocol.
Q7: How to create thread-safe decorators?
Answer: Usethreading.Lockfor thread-safe decorators: ``python import threading from functools import wraps def thread_safe_cache(func): cache = {} lock = threading.Lock() @wraps(func) def wrapper(*args): with lock: if args not in cache: cache[args] = func(*args) return cache[args] return wrapper`` The Lock ensures that only one thread reads/writes the cache at a time — avoiding race conditions.
Q8: What is the best practice for exception handling inside a decorator?
Answer: Best practices: - Re-raise the original exception (raise) after logging — don't suppress it - Usetry/except/finallyfor cleanup (e.g., close connections) - Custom exceptions mat introduce karo unless clearly documented ho - Useexcept Exceptioninstead of bareexcept:(avoid catching BaseException) - Log exception info but let it propagate to the user
Q9: functools.lru_cache vs custom memoize decorator — production mein kaun better hai?
Answer:lru_cacheis better in production because: - Memory bounded hai (maxsizeparameter) - Thread-safe hai (internal locking) -cache_info()provides hit/miss stats -cache_clear()can clear programmatically - C-optimized implementation hai — faster than pure Python - Custom memoize is fine only for learning/specific cases
Q10: What is the fundamental difference between the Decorator pattern and Monkey Patching?
Answer: | Aspect | Decorator | Monkey Patching | |--------|-----------|-----------------| | Timing | Definition time pe apply | Runtime pe kisi bhi waqt | | Scope | Only the decorated function | Any object/module | | Explicit |@syntax se clearly visible | Hidden, hard to trace | | Reversible |.__wrapped__se original accessible | Usually irreversible | | Best Practice | ✅ Recommended pattern | ⚠️ Avoid in production | Decorator clean, explicit, predictable hai. Monkey patching risky, implicit, debugging nightmare hai.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Decorators in Python.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Python Master Course topic.
Search Terms
python-master-course, python master course, python, master, course, functions, decorators, decorators in python
Related Python Master Course Topics
Continue learning this concept
FunctionsFunction Arguments in PythonComplete guide to Python function arguments — positional, keyword, default, *args, **kwargs and their real-world use cases. Understand argument types with best practices.FunctionsFunctions Basics in PythonPython mein functions ki complete guide — definition, calling, types, aur best practices. Samjho functions ka syntax, purpose, aur real-world usage Hindi explanations ke saath.FunctionsGenerators in PythonPython generators ki detailed guide — yield keyword, generator functions, generator expressions, send/throw/close, aur real-world use cases jaise infinite sequences, data pipelines, aur memory-efficient file processing. Hindi mein.FunctionsLambda Functions in PythonComplete guide to Python lambda functions — syntax, use cases, usage with map/filter/sorted, and comparison with regular functions. Learn with practical examples.FunctionsRecursive Functions in PythonPython mein recursion ki complete guide — base case, recursive case, call stack, fibonacci, factorial, binary search aur tree traversal ke saath Hindi explanations.FunctionsReturn Statement in PythonPython mein return statement ki poori guide — single/multiple return values, early return, None return, aur real-world patterns. Hindi mein samjho return ke saare use cases.