۱۰

مدیریت خطا و Exception ⚠️

یاد می‌گیریم چطور خطاها رو مدیریت کنیم و برنامه‌های ضدضربه بنویسیم!

چرا مدیریت خطا مهمه؟

تو برنامه‌نویسی، خطاها اجتناب‌ناپذیرن. بیا ببینیم چه خطاهایی ممکنه رخ بده:

انواع خطاهای رایج
# خطای تقسیم بر صفر
result = 10 / 0  # ZeroDivisionError

# خطای دسترسی به ایندکس نامعتبر
my_list = [1, 2, 3]
print(my_list[10])  # IndexError

# خطای تبدیل نوع داده
number = int("سلام")  # ValueError

# خطای دسترسی به کلید ناموجود
my_dict = {"نام": "علی"}
print(my_dict["سن"])  # KeyError

# خطای باز کردن فایل ناموجود
with open("فایل_ناموجود.txt", "r") as file:  # FileNotFoundError
    content = file.read()

# خطای تعریف نشده متغیر
print(undefined_variable)  # NameError

🚨 بدون مدیریت خطا، برنامه کرش می‌کنه و کاربر تجربه بدی داره!

try-except: نجات‌دهنده برنامه‌ها

با try-except می‌تونیم خطاها رو بگیریم و مدیریت کنیم:

ساختار پایه try-except
# مثال ساده
try:
    number = int(input("یک عدد وارد کنید: "))
    result = 100 / number
    print(f"نتیجه: {result}")
except ValueError:
    print("❌ لطفاً یک عدد معتبر وارد کنید!")
except ZeroDivisionError:
    print("❌ نمی‌توان بر صفر تقسیم کرد!")

print("برنامه ادامه پیدا کرد! ✅")

# گرفتن چند نوع خطا با یک except
try:
    # کدی که ممکنه خطا بده
    data = eval(input("عبارت ریاضی وارد کنید: "))
    print(f"نتیجه: {data}")
except (ValueError, SyntaxError, NameError) as error:
    print(f"خطا در ورودی: {error}")

# گرفتن همه خطاها
try:
    risky_operation()
except Exception as e:
    print(f"خطای غیرمنتظره: {e}")

💡 نکته: همیشه خطاهای خاص رو قبل از Exception عمومی بنویس!

else و finally: کنترل کامل جریان

else و finally کنترل بیشتری روی جریان برنامه می‌دن:

try-except-else-finally
def safe_divide(a, b):
    try:
        result = a / b
    except ZeroDivisionError:
        print("❌ تقسیم بر صفر امکان‌پذیر نیست!")
        return None
    except TypeError:
        print("❌ نوع داده نامعتبر!")
        return None
    else:
        # فقط اگر خطایی رخ نداده باشد اجرا می‌شود
        print("✅ تقسیم با موفقیت انجام شد")
        return result
    finally:
        # همیشه اجرا می‌شود (خطا یا بدون خطا)
        print("🔄 عملیات تقسیم تمام شد")

# تست تابع
print("=== تست ۱: تقسیم عادی ===")
result1 = safe_divide(10, 2)
print(f"نتیجه: {result1}")

print("\n=== تست ۲: تقسیم بر صفر ===")
result2 = safe_divide(10, 0)
print(f"نتیجه: {result2}")

print("\n=== تست ۳: نوع داده اشتباه ===")
result3 = safe_divide("10", "abc")
print(f"نتیجه: {result3}")

📝 یادداشت:

  • else: فقط اگر خطایی رخ نداده باشد اجرا می‌شود
  • finally: همیشه اجرا می‌شود (برای تمیزکاری منابع)

ایجاد Exception های سفارشی

می‌تونیم خطاهای سفارشی خودمون رو تعریف کنیم:

Exception های سفارشی
# تعریف Exception سفارشی
class AgeError(Exception):
    """خطای سن نامعتبر"""
    def __init__(self, age, message="سن وارد شده نامعتبر است"):
        self.age = age
        self.message = message
        super().__init__(self.message)

class PasswordError(Exception):
    """خطای رمز عبور ضعیف"""
    pass

# استفاده از Exception های سفارشی
def validate_user(name, age, password):
    # بررسی سن
    if age < 0 or age > 150:
        raise AgeError(age, f"سن {age} غیرمنطقی است!")
    
    # بررسی رمز عبور
    if len(password) < 8:
        raise PasswordError("رمز عبور باید حداقل ۸ کاراکتر باشد!")
    
    if not any(c.isdigit() for c in password):
        raise PasswordError("رمز عبور باید حداقل یک عدد داشته باشد!")
    
    return f"کاربر {name} با موفقیت ثبت شد! ✅"

# تست تابع
users_data = [
    ("علی", 25, "mypassword123"),
    ("سارا", -5, "weakpass"),
    ("محمد", 30, "short"),
    ("فاطمه", 200, "verylongpassword123")
]

for name, age, password in users_data:
    try:
        result = validate_user(name, age, password)
        print(result)
    except AgeError as e:
        print(f"❌ خطای سن برای {name}: {e}")
    except PasswordError as e:
        print(f"❌ خطای رمز عبور برای {name}: {e}")
    except Exception as e:
        print(f"❌ خطای غیرمنتظره: {e}")

مثال کاربردی: مدیریت فایل با خطایابی

بیا یه برنامه کاربردی برای مدیریت فایل بنویسیم:

مدیر فایل هوشمند
import json
import os
from datetime import datetime

class FileManager:
    def __init__(self):
        self.log_file = "file_operations.log"
    
    def log_operation(self, operation, status, details=""):
        """ثبت عملیات در فایل لاگ"""
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        log_entry = f"[{timestamp}] {operation}: {status} - {details}\n"
        
        try:
            with open(self.log_file, "a", encoding="utf-8") as log:
                log.write(log_entry)
        except Exception as e:
            print(f"خطا در ثبت لاگ: {e}")
    
    def read_json_file(self, filename):
        """خواندن فایل JSON با مدیریت خطا"""
        try:
            # بررسی وجود فایل
            if not os.path.exists(filename):
                raise FileNotFoundError(f"فایل {filename} یافت نشد")
            
            # بررسی اندازه فایل
            file_size = os.path.getsize(filename)
            if file_size == 0:
                raise ValueError("فایل خالی است")
            
            # خواندن فایل
            with open(filename, "r", encoding="utf-8") as file:
                data = json.load(file)
            
            self.log_operation("READ", "SUCCESS", f"فایل {filename} خوانده شد")
            return data
            
        except FileNotFoundError as e:
            self.log_operation("READ", "ERROR", str(e))
            print(f"❌ {e}")
            return None
            
        except json.JSONDecodeError as e:
            self.log_operation("READ", "ERROR", f"فرمت JSON نامعتبر: {e}")
            print(f"❌ فرمت فایل نامعتبر: {e}")
            return None
            
        except ValueError as e:
            self.log_operation("READ", "ERROR", str(e))
            print(f"❌ {e}")
            return None
            
        except Exception as e:
            self.log_operation("READ", "ERROR", f"خطای غیرمنتظره: {e}")
            print(f"❌ خطای غیرمنتظره: {e}")
            return None
    
    def write_json_file(self, filename, data):
        """نوشتن فایل JSON با مدیریت خطا"""
        try:
            # بررسی نوع داده
            if not isinstance(data, (dict, list)):
                raise TypeError("داده باید از نوع dict یا list باشد")
            
            # ایجاد backup اگر فایل وجود داشته باشد
            if os.path.exists(filename):
                backup_name = f"{filename}.backup"
                os.rename(filename, backup_name)
                print(f"📁 فایل قبلی به {backup_name} منتقل شد")
            
            # نوشتن فایل جدید
            with open(filename, "w", encoding="utf-8") as file:
                json.dump(data, file, ensure_ascii=False, indent=2)
            
            self.log_operation("WRITE", "SUCCESS", f"فایل {filename} نوشته شد")
            print(f"✅ فایل {filename} با موفقیت ذخیره شد")
            return True
            
        except TypeError as e:
            self.log_operation("WRITE", "ERROR", str(e))
            print(f"❌ {e}")
            return False
            
        except PermissionError:
            self.log_operation("WRITE", "ERROR", "عدم دسترسی برای نوشتن")
            print("❌ دسترسی برای نوشتن فایل وجود ندارد")
            return False
            
        except Exception as e:
            self.log_operation("WRITE", "ERROR", f"خطای غیرمنتظره: {e}")
            print(f"❌ خطای غیرمنتظره: {e}")
            return False

# تست FileManager
fm = FileManager()

# تست نوشتن
test_data = {
    "نام": "علی احمدی",
    "سن": 28,
    "شهر": "تهران",
    "مهارت‌ها": ["پایتون", "جاوااسکریپت", "React"]
}

fm.write_json_file("user_data.json", test_data)

# تست خواندن
loaded_data = fm.read_json_file("user_data.json")
if loaded_data:
    print(f"داده خوانده شده: {loaded_data}")

# تست خواندن فایل ناموجود
fm.read_json_file("nonexistent.json")

تمرین عملی: ماشین حساب ضدضربه

یک ماشین حساب بنویس که تمام خطاهای ممکن رو مدیریت کنه:

  • ورودی‌های نامعتبر (غیرعددی)
  • تقسیم بر صفر
  • عملگرهای نامعتبر
  • محدوده عددی (اعداد خیلی بزرگ)
ماشین حساب ضدضربه
class SafeCalculator:
    def __init__(self):
        self.max_number = 10**10  # حداکثر عدد مجاز
    
    def validate_number(self, num_str):
        """اعتبارسنجی عدد ورودی"""
        try:
            num = float(num_str)
            if abs(num) > self.max_number:
                raise OverflowError(f"عدد بیش از حد مجاز ({self.max_number})")
            return num
        except ValueError:
            raise ValueError(f"'{num_str}' یک عدد معتبر نیست")
    
    def calculate(self, num1_str, operator, num2_str):
        """انجام محاسبه با مدیریت خطا"""
        try:
            # اعتبارسنجی اعداد
            num1 = self.validate_number(num1_str)
            num2 = self.validate_number(num2_str)
            
            # انجام عملیات
            if operator == "+":
                result = num1 + num2
            elif operator == "-":
                result = num1 - num2
            elif operator == "*":
                result = num1 * num2
            elif operator == "/":
                if num2 == 0:
                    raise ZeroDivisionError("تقسیم بر صفر امکان‌پذیر نیست")
                result = num1 / num2
            elif operator == "**":
                if num1 == 0 and num2 < 0:
                    raise ValueError("صفر به توان منفی تعریف نشده")
                result = num1 ** num2
            else:
                raise ValueError(f"عملگر '{operator}' پشتیبانی نمی‌شود")
            
            # بررسی محدوده نتیجه
            if abs(result) > self.max_number:
                raise OverflowError("نتیجه بیش از حد مجاز")
            
            return result
            
        except (ValueError, ZeroDivisionError, OverflowError) as e:
            return f"❌ خطا: {e}"
        except Exception as e:
            return f"❌ خطای غیرمنتظره: {e}"

# تست ماشین حساب
calc = SafeCalculator()

test_cases = [
    ("10", "+", "5"),
    ("20", "/", "0"),
    ("abc", "*", "5"),
    ("2", "**", "100"),
    ("10", "&", "5"),
    ("0", "**", "-1")
]

for num1, op, num2 in test_cases:
    result = calc.calculate(num1, op, num2)
    print(f"{num1} {op} {num2} = {result}")

چالش پیشرفته: سیستم لاگین امن

یک سیستم لاگین بنویس که:

  • تلاش‌های ناموفق رو محدود کنه
  • کاربر رو بعد از ۳ تلاش اشتباه قفل کنه
  • تمام فعالیت‌ها رو لاگ کنه
  • خطاهای مختلف رو مدیریت کنه
سیستم لاگین امن
import hashlib
import json
from datetime import datetime, timedelta

class SecureLoginSystem:
    def __init__(self):
        self.users_file = "users.json"
        self.log_file = "login_log.txt"
        self.max_attempts = 3
        self.lockout_duration = 30  # دقیقه
        self.load_users()
    
    def hash_password(self, password):
        """هش کردن رمز عبور"""
        return hashlib.sha256(password.encode()).hexdigest()
    
    def log_activity(self, username, activity, status):
        """ثبت فعالیت در لاگ"""
        try:
            timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            log_entry = f"[{timestamp}] {username}: {activity} - {status}\n"
            with open(self.log_file, "a", encoding="utf-8") as log:
                log.write(log_entry)
        except Exception as e:
            print(f"خطا در ثبت لاگ: {e}")
    
    def load_users(self):
        """بارگذاری کاربران از فایل"""
        try:
            with open(self.users_file, "r", encoding="utf-8") as file:
                self.users = json.load(file)
        except FileNotFoundError:
            self.users = {}
            self.save_users()
        except json.JSONDecodeError:
            print("❌ فایل کاربران خراب است")
            self.users = {}
    
    def save_users(self):
        """ذخیره کاربران در فایل"""
        try:
            with open(self.users_file, "w", encoding="utf-8") as file:
                json.dump(self.users, file, ensure_ascii=False, indent=2)
        except Exception as e:
            print(f"خطا در ذخیره کاربران: {e}")
    
    def register_user(self, username, password):
        """ثبت‌نام کاربر جدید"""
        try:
            if not username or not password:
                raise ValueError("نام کاربری و رمز عبور نمی‌تواند خالی باشد")
            
            if len(password) < 6:
                raise ValueError("رمز عبور باید حداقل ۶ کاراکتر باشد")
            
            if username in self.users:
                raise ValueError("این نام کاربری قبلاً ثبت شده")
            
            self.users[username] = {
                "password": self.hash_password(password),
                "failed_attempts": 0,
                "locked_until": None,
                "created_at": datetime.now().isoformat()
            }
            
            self.save_users()
            self.log_activity(username, "REGISTER", "SUCCESS")
            return True, "کاربر با موفقیت ثبت شد"
            
        except ValueError as e:
            self.log_activity(username, "REGISTER", f"FAILED: {e}")
            return False, str(e)
        except Exception as e:
            self.log_activity(username, "REGISTER", f"ERROR: {e}")
            return False, f"خطای غیرمنتظره: {e}"
    
    def is_user_locked(self, username):
        """بررسی قفل بودن کاربر"""
        if username not in self.users:
            return False
        
        locked_until = self.users[username].get("locked_until")
        if not locked_until:
            return False
        
        unlock_time = datetime.fromisoformat(locked_until)
        if datetime.now() > unlock_time:
            # زمان قفل تمام شده
            self.users[username]["locked_until"] = None
            self.users[username]["failed_attempts"] = 0
            self.save_users()
            return False
        
        return True
    
    def login(self, username, password):
        """ورود کاربر"""
        try:
            if not username or not password:
                raise ValueError("نام کاربری و رمز عبور الزامی است")
            
            if username not in self.users:
                raise ValueError("نام کاربری یافت نشد")
            
            # بررسی قفل بودن
            if self.is_user_locked(username):
                locked_until = self.users[username]["locked_until"]
                unlock_time = datetime.fromisoformat(locked_until)
                remaining = unlock_time - datetime.now()
                minutes = int(remaining.total_seconds() / 60)
                raise ValueError(f"حساب قفل است. {minutes} دقیقه صبر کنید")
            
            # بررسی رمز عبور
            hashed_password = self.hash_password(password)
            if self.users[username]["password"] != hashed_password:
                # افزایش تعداد تلاش‌های ناموفق
                self.users[username]["failed_attempts"] += 1
                
                if self.users[username]["failed_attempts"] >= self.max_attempts:
                    # قفل کردن حساب
                    lock_until = datetime.now() + timedelta(minutes=self.lockout_duration)
                    self.users[username]["locked_until"] = lock_until.isoformat()
                    self.save_users()
                    self.log_activity(username, "LOGIN", "LOCKED")
                    raise ValueError(f"حساب به دلیل {self.max_attempts} تلاش ناموفق قفل شد")
                
                self.save_users()
                attempts_left = self.max_attempts - self.users[username]["failed_attempts"]
                self.log_activity(username, "LOGIN", "FAILED")
                raise ValueError(f"رمز عبور اشتباه. {attempts_left} تلاش باقی مانده")
            
            # ورود موفق
            self.users[username]["failed_attempts"] = 0
            self.users[username]["last_login"] = datetime.now().isoformat()
            self.save_users()
            self.log_activity(username, "LOGIN", "SUCCESS")
            return True, "ورود موفقیت‌آمیز"
            
        except ValueError as e:
            return False, str(e)
        except Exception as e:
            self.log_activity(username, "LOGIN", f"ERROR: {e}")
            return False, f"خطای غیرمنتظره: {e}"

# تست سیستم لاگین
auth = SecureLoginSystem()

# ثبت‌نام
print("=== ثبت‌نام ===")
success, msg = auth.register_user("ali123", "mypassword")
print(f"ثبت‌نام: {msg}")

# تلاش‌های ورود
print("\n=== تلاش‌های ورود ===")
test_logins = [
    ("ali123", "wrongpass"),
    ("ali123", "wrongpass"),
    ("ali123", "wrongpass"),
    ("ali123", "mypassword")
]

for username, password in test_logins:
    success, msg = auth.login(username, password)
    print(f"ورود {username}: {msg}")