Decorators و Context Managers 🎭
کدهامون رو با ابزارهای قدرتمند پایتون تمیز و حرفهای میکنیم!
🎭 Decorators: جادوگران کد!
Decorator ها مثل جادوگرهای کد هستن! اونا میتونن بدون تغییر دادن کد اصلی، قابلیتهای جدید بهش اضافه کنن. مثلاً میتونن زمان اجرای یه تابع رو اندازه بگیرن، لاگ بگیرن، یا حتی کش کنن. Decorator ها با علامت @ شروع میشن و روی تابع یا کلاس قرار میگیرن.
# یک decorator ساده برای اندازهگیری زمان اجرا
import time
import functools
def timer(func):
"""Decorator برای اندازهگیری زمان اجرای تابع"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"⏱️ تابع {func.__name__} در {end_time - start_time:.4f} ثانیه اجرا شد")
return result
return wrapper
# استفاده از decorator
@timer
def slow_function():
"""یک تابع که کمی طول میکشه"""
time.sleep(1)
return "کار تمام شد!"
@timer
def calculate_sum(n):
"""محاسبه مجموع اعداد تا n"""
return sum(range(1, n + 1))
# تست کردن
result1 = slow_function()
print(f"نتیجه: {result1}")
result2 = calculate_sum(1000000)
print(f"مجموع: {result2}")
# خروجی:
# ⏱️ تابع slow_function در 1.0012 ثانیه اجرا شد
# نتیجه: کار تمام شد!
# ⏱️ تابع calculate_sum در 0.0234 ثانیه اجرا شد
# مجموع: 500000500000
🔐 Context Managers: مدیران حرفهای!
Context Manager ها مثل مدیرهای حرفهای هستن که مراقب منابع سیستم هستن! اونا اطمینان حاصل میکنن که فایلها بسته بشن، اتصالات پایگاه داده قطع بشن، و کلاً همه چیز تمیز و مرتب باشه. با کلمه کلیدی `with` استفاده میشن و خیلی کار رو راحت میکنن.
# روش قدیمی - خطرناک!
def old_way():
file = open('data.txt', 'w')
file.write('سلام دنیا!')
# اگه اینجا خطایی بیفته، فایل بسته نمیشه! 😱
file.close()
# روش جدید - امن و تمیز!
def new_way():
with open('data.txt', 'w') as file:
file.write('سلام دنیا!')
# حتی اگه خطا بیفته، فایل خودکار بسته میشه! 😎
# مثال پیشرفتهتر
import time
from contextlib import contextmanager
@contextmanager
def timer_context(name):
"""Context manager برای اندازهگیری زمان"""
print(f"🚀 شروع {name}...")
start_time = time.time()
try:
yield # اینجا کد اصلی اجرا میشه
finally:
end_time = time.time()
print(f"⏱️ {name} در {end_time - start_time:.4f} ثانیه تمام شد")
# استفاده از context manager سفارشی
with timer_context("محاسبات سنگین"):
result = sum(i**2 for i in range(1000000))
print(f"نتیجه: {result}")
# خروجی:
# 🚀 شروع محاسبات سنگین...
# نتیجه: 333332833333500000
# ⏱️ محاسبات سنگین در 0.1234 ثانیه تمام شد
🎪 Decorators پیشرفته
حالا که با Decorator ها آشنا شدیم، بیاین چندتا مثال پیشرفتهتر ببینیم! Decorator هایی که پارامتر میگیرن، کش میکنن، و حتی کلاسها رو تغییر میدن.
import functools
import time
from typing import Any, Callable
# Decorator با پارامتر
def retry(max_attempts: int = 3, delay: float = 1.0):
"""Decorator برای تلاش مجدد در صورت خطا"""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts - 1:
raise e
print(f"❌ تلاش {attempt + 1} ناموفق: {e}")
time.sleep(delay)
return None
return wrapper
return decorator
# Cache decorator
def cache(func: Callable) -> Callable:
"""Decorator برای کش کردن نتایج"""
cached_results = {}
@functools.wraps(func)
def wrapper(*args, **kwargs):
# ساخت کلید برای کش
key = str(args) + str(sorted(kwargs.items()))
if key in cached_results:
print(f"💾 نتیجه از کش: {func.__name__}")
return cached_results[key]
result = func(*args, **kwargs)
cached_results[key] = result
print(f"🔄 محاسبه جدید: {func.__name__}")
return result
return wrapper
# استفاده از decorators
@retry(max_attempts=3, delay=0.5)
def unreliable_api_call():
"""تابعی که گاهی خطا میده"""
import random
if random.random() < 0.7: # 70% احتمال خطا
raise Exception("اتصال به API ناموفق!")
return "دادهها دریافت شد!"
@cache
def fibonacci(n: int) -> int:
"""محاسبه فیبوناچی با کش"""
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
# تست کردن
print("=== تست Retry Decorator ===")
try:
result = unreliable_api_call()
print(f"✅ موفق: {result}")
except Exception as e:
print(f"❌ نهایتاً ناموفق: {e}")
print("\n=== تست Cache Decorator ===")
print(f"fibonacci(10) = {fibonacci(10)}")
print(f"fibonacci(10) = {fibonacci(10)}") # این بار از کش
🏋️♂️ تمرین: سیستم لاگ هوشمند
یه Decorator بنویس که تمام فراخوانیهای تابع رو لاگ کنه و یه Context Manager که فایل لاگ رو مدیریت کنه. هدف اینه که بتونیم ببینیم چه تابعهایی چه زمانی فراخوانی شدن و چقدر طول کشیدن.
import time
import functools
from contextlib import contextmanager
from datetime import datetime
# Decorator برای لاگ کردن
def log_calls(func):
"""Decorator که فراخوانیهای تابع رو لاگ میکنه"""
# کدت رو اینجا بنویس
pass
# Context Manager برای مدیریت فایل لاگ
@contextmanager
def log_file_manager(filename):
"""Context Manager برای مدیریت فایل لاگ"""
# کدت رو اینجا بنویس
pass
# تست کردن
@log_calls
def calculate_sum(numbers):
time.sleep(0.1) # شبیهسازی محاسبه
return sum(numbers)
@log_calls
def find_max(numbers):
time.sleep(0.05)
return max(numbers)
# استفاده
with log_file_manager("app.log"):
result1 = calculate_sum([1, 2, 3, 4, 5])
result2 = find_max([10, 5, 8, 3, 12])
print(f"مجموع: {result1}, حداکثر: {result2}")
جواب تمرین
import time
import functools
from contextlib import contextmanager
from datetime import datetime
# متغیر سراسری برای نگهداری فایل لاگ
log_file = None
def log_calls(func):
"""Decorator که فراخوانیهای تابع رو لاگ میکنه"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# لاگ شروع
log_message = f"[{timestamp}] 🚀 شروع {func.__name__} با آرگومانهای {args}\n"
print(log_message.strip())
if log_file:
log_file.write(log_message)
log_file.flush()
try:
result = func(*args, **kwargs)
end_time = time.time()
duration = end_time - start_time
# لاگ موفقیت
success_message = f"[{timestamp}] ✅ {func.__name__} موفق - مدت: {duration:.4f}s - نتیجه: {result}\n"
print(success_message.strip())
if log_file:
log_file.write(success_message)
log_file.flush()
return result
except Exception as e:
end_time = time.time()
duration = end_time - start_time
# لاگ خطا
error_message = f"[{timestamp}] ❌ {func.__name__} ناموفق - مدت: {duration:.4f}s - خطا: {e}\n"
print(error_message.strip())
if log_file:
log_file.write(error_message)
log_file.flush()
raise
return wrapper
@contextmanager
def log_file_manager(filename):
"""Context Manager برای مدیریت فایل لاگ"""
global log_file
print(f"📝 باز کردن فایل لاگ: {filename}")
log_file = open(filename, 'a', encoding='utf-8')
try:
log_file.write(f"\n=== شروع جلسه لاگ در {datetime.now()} ===\n")
log_file.flush()
yield log_file
finally:
log_file.write(f"=== پایان جلسه لاگ در {datetime.now()} ===\n\n")
log_file.close()
log_file = None
print(f"📝 بستن فایل لاگ: {filename}")
# تست کردن
@log_calls
def calculate_sum(numbers):
time.sleep(0.1) # شبیهسازی محاسبه
return sum(numbers)
@log_calls
def find_max(numbers):
time.sleep(0.05)
return max(numbers)
# استفاده
with log_file_manager("app.log"):
result1 = calculate_sum([1, 2, 3, 4, 5])
result2 = find_max([10, 5, 8, 3, 12])
print(f"\n🎯 نتایج نهایی - مجموع: {result1}, حداکثر: {result2}")
🚀 چالش: سیستم کش پیشرفته
یه سیستم کش پیشرفته بساز که بتونه نتایج رو برای مدت زمان مشخصی نگه داره، حافظه رو مدیریت کنه، و آمار استفاده رو نمایش بده. از هم Decorator و هم Context Manager استفاده کن!