۱۴

Decorators و Context Managers 🎭

کدهامون رو با ابزارهای قدرتمند پایتون تمیز و حرفه‌ای می‌کنیم!

🎭 Decorators: جادوگران کد!

Decorator ها مثل جادوگرهای کد هستن! اونا می‌تونن بدون تغییر دادن کد اصلی، قابلیت‌های جدید بهش اضافه کنن. مثلاً می‌تونن زمان اجرای یه تابع رو اندازه بگیرن، لاگ بگیرن، یا حتی کش کنن. 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` استفاده می‌شن و خیلی کار رو راحت می‌کنن.

استفاده از Context Manager
# روش قدیمی - خطرناک!
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 هایی که پارامتر می‌گیرن، کش می‌کنن، و حتی کلاس‌ها رو تغییر می‌دن.

Decorators پیشرفته
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 استفاده کن!