~/icsd.ir — bash
SYSTEM_ONLINE

مدیریت خطا (Exceptions)

کد بدون مدیریت خطا، کد ناتمام است. در این فصل یاد می‌گیریم چگونه با try/except خطاها را مدیریت کنیم و برنامه‌های پایدار بسازیم.

کد بدون مدیریت خطا، کد ناتمام است. در این فصل یاد می‌گیریم چگونه با try/except خطاها را مدیریت کنیم و برنامه‌های پایدار بسازیم.

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

# بدون مدیریت خطا
age = int(input("سن: "))    # اگر کاربر عدد ندهد؟ کرش!
result = 10 / int(input("مخرج: "))   # اگر صفر باشد؟ کرش!

# با مدیریت خطا
try:
    age = int(input("سن: "))
    print(f"سن شما {age} است")
except ValueError:
    print("لطفاً یک عدد وارد کنید!")

ساختار try/except

try:
    # کدی که ممکن است خطا بدهد
    num = int(input("یک عدد: "))
    result = 100 / num
    print(f"نتیجه: {result}")
except ValueError:
    print("ورودی عدد نیست!")
except ZeroDivisionError:
    print("نمی‌توان بر صفر تقسیم کرد!")
except Exception as e:
    # هر خطای دیگر
    print(f"خطای ناشناخته: {e}")

خطاهای رایج

خطا زمان رخ دادن
ValueError تبدیل نوع نامعتبر: int("abc")
TypeError عملیات روی نوع اشتباه: "a" + 5
NameError متغیر تعریف نشده
IndexError اندیس خارج محدوده لیست
KeyError کلید موجود نیست در dict
AttributeError متد یا فیلد ناموجود
FileNotFoundError فایل پیدا نشد
PermissionError دسترسی نداریم
ZeroDivisionError تقسیم بر صفر
ImportError import نشد
KeyboardInterrupt کاربر Ctrl+C زد
RuntimeError خطای زمان اجرا

تست خطاها

# ValueError
int("abc")              # ValueError

# TypeError
"hello" + 5             # TypeError
len(42)                 # TypeError

# IndexError
nums = [1, 2, 3]
nums[10]                # IndexError

# KeyError
d = {"name": "ali"}
d["age"]                # KeyError

# FileNotFoundError
open("not_exist.txt")   # FileNotFoundError

# AttributeError
"hello".reverse()       # AttributeError (str.reverse وجود ندارد)

چندین except

try:
    file = open("data.json")
    data = json.load(file)
    age = data["age"]
    result = 100 / age
except FileNotFoundError:
    print("فایل پیدا نشد")
except json.JSONDecodeError:
    print("فایل JSON خراب است")
except KeyError:
    print("کلید 'age' وجود ندارد")
except ZeroDivisionError:
    print("سن نمی‌تواند صفر باشد")
except Exception as e:
    # همه خطاهای دیگر
    print(f"خطای غیرمنتظره: {type(e).__name__}: {e}")

چند خطا در یک except

try:
    value = int(input("عدد: "))
    result = 10 / value
except (ValueError, ZeroDivisionError) as e:
    print(f"ورودی نامعتبر: {e}")

else و finally

try:
    file = open("data.txt", "r", encoding="utf-8")
    data = file.read()
except FileNotFoundError:
    print("فایل پیدا نشد")
else:
    # فقط اگر هیچ خطایی نبود اجرا می‌شود
    print(f"محتوا: {data}")
    print("خواندن موفق")
finally:
    # همیشه اجرا می‌شود (با خطا یا بدون خطا)
    print("اتمام عملیات")
    try:
        file.close()
    except:
        pass

الگوی صحیح

try:
    file = open("data.txt", "r")
except FileNotFoundError:
    print("ساخت فایل جدید...")
    file = open("data.txt", "w")
else:
    print("فایل پیدا شد")
finally:
    file.close()

# روش بهتر: with statement!
try:
    with open("data.txt", "r") as file:
        data = file.read()
except FileNotFoundError:
    print("فایل نبود")

پرتاب خطا با raise

def divide(a, b):
    if b == 0:
        raise ValueError("مخرج نمی‌تواند صفر باشد")
    return a / b

try:
    result = divide(10, 0)
except ValueError as e:
    print(f"خطا: {e}")

# پرتاب مجدد
def process(data):
    try:
        # ...
        result = risky_operation(data)
    except Exception as e:
        print(f"لاگ: {e}")
        raise    # پرتاب مجدد همان خطا

# تبدیل خطا
def load_config():
    try:
        with open("config.json") as f:
            return json.load(f)
    except FileNotFoundError as e:
        raise RuntimeError("کانفیگ تنظیم نشده") from e

خطاهای سفارشی

class InsufficientFundsError(Exception):
    """خطای موجودی ناکافی"""
    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
        super().__init__(
            f"موجودی ({balance}) برای برداشت {amount} کافی نیست"
        )

class InvalidAgeError(ValueError):
    """خطای سن نامعتبر"""
    pass

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFundsError(balance, amount)
    return balance - amount

def register(name, age):
    if not (0 < age < 150):
        raise InvalidAgeError(f"سن {age} نامعتبر است")
    return {"name": name, "age": age}

# استفاده
try:
    new_balance = withdraw(1000, 5000)
except InsufficientFundsError as e:
    print(f"خطا: {e}")
    print(f"موجودی: {e.balance}, درخواستی: {e.amount}")

الگوهای متداول

اعتبارسنجی ورودی با حلقه

while True:
    try:
        age = int(input("سن (1-120): "))
        if 0 < age < 120:
            break
        print("سن باید بین ۱ تا ۱۲۰ باشد")
    except ValueError:
        print("لطفاً یک عدد وارد کنید")

print(f"سن شما: {age}")

EAFP – روش پایتونیک

EAFP = Easier to Ask for Forgiveness than Permission

data = {"name": "علی"}

# روش LBYL (Look Before You Leap) - غیرپایتونیک
if "age" in data:
    print(data["age"])
else:
    print("سن نداریم")

# روش EAFP (پایتونیک)
try:
    print(data["age"])
except KeyError:
    print("سن نداریم")

# یا ساده‌تر با get
print(data.get("age", "سن نداریم"))

Resource Cleanup

import sqlite3

# روش غلط
conn = sqlite3.connect("db.sqlite")
cursor = conn.cursor()
cursor.execute("SELECT * FROM users")
results = cursor.fetchall()
conn.close()    # اگر بالا خطا بدهد، این اجرا نمی‌شود!

# روش درست
try:
    conn = sqlite3.connect("db.sqlite")
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM users")
    results = cursor.fetchall()
finally:
    conn.close()    # حتماً اجرا می‌شود

# روش پایتونیک
with sqlite3.connect("db.sqlite") as conn:
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM users")
    results = cursor.fetchall()
# اتصال خودکار بسته می‌شود

بهترین شیوه‌ها

✅ کارهای درست:

  • خطاهای خاص را بگیرید (نه فقط except)
  • پیام خطای دقیق و مفید بدهید
  • خطاها را لاگ کنید
  • از finally یا with برای cleanup استفاده کنید
  • خطاهای سفارشی را از Exception ارث ببرید
❌ کارهای اشتباه:

# بد: همه چیز را قورت می‌دهد
try:
    risky()
except:
    pass    # ← هرگز این کار را نکنید!

# بد: گرفتن Exception عام بدون لاگ
try:
    risky()
except Exception:
    return None   # ← دیباگ غیرممکن می‌شود

# خوب: حداقل لاگ کنید
import logging
try:
    risky()
except Exception as e:
    logging.exception("خطا در risky()")
    raise

مثال واقعی: سیستم بانکی

class BankError(Exception):
    """کلاس پایه خطاهای بانکی"""
    pass

class InsufficientFunds(BankError):
    pass

class AccountNotFound(BankError):
    pass

class InvalidAmount(BankError):
    pass

class Bank:
    def __init__(self):
        self.accounts = {}
    
    def create(self, account_id, balance=0):
        if account_id in self.accounts:
            raise BankError(f"حساب {account_id} وجود دارد")
        if balance < 0:
            raise InvalidAmount("موجودی منفی")
        self.accounts[account_id] = balance
    
    def withdraw(self, account_id, amount):
        if account_id not in self.accounts:
            raise AccountNotFound(f"حساب {account_id} نیست")
        if amount <= 0:
            raise InvalidAmount("مبلغ باید مثبت باشد")
        if amount > self.accounts[account_id]:
            raise InsufficientFunds(
                f"موجودی: {self.accounts[account_id]}, درخواست: {amount}"
            )
        self.accounts[account_id] -= amount
        return self.accounts[account_id]

bank = Bank()
bank.create("A001", 10000)

try:
    new_balance = bank.withdraw("A001", 15000)
except InsufficientFunds as e:
    print(f"موجودی ناکافی: {e}")
except AccountNotFound as e:
    print(f"حساب نامعتبر: {e}")
except BankError as e:
    print(f"خطای بانکی: {e}")

تمرین فصل

🏋️ تمرین ۱: برنامه‌ای بنویسید که از کاربر دو عدد بگیرد و تقسیم آن‌ها را با مدیریت کامل خطا (ورودی غیرعدد، تقسیم بر صفر) چاپ کند.
🏋️ تمرین ۲: تابعی بنویسید که فایل JSON را بخواند و در صورت بروز هر خطا (FileNotFoundError، JSONDecodeError)، پیام مناسب چاپ کند.
🏋️ تمرین ۳: یک خطای سفارشی به نام InvalidEmailError بسازید و در تابعی برای اعتبارسنجی ایمیل استفاده کنید.
🏋️ تمرین ۴: ماشین‌حسابی بنویسید که تا زمانی که کاربر نخواهد، در حلقه می‌ماند و در صورت بروز خطا، به‌جای کرش، پیام نشان داده و دوباره می‌پرسد.

جمع‌بندی

  • ساختار try/except برای مدیریت خطا
  • خطاهای رایج: ValueError، TypeError، KeyError، FileNotFoundError و…
  • چندین except برای انواع خطا
  • else (موفقیت) و finally (همیشه)
  • پرتاب خطا با raise و زنجیره با raise from
  • ساخت خطاهای سفارشی
  • الگوی EAFP در پایتون

نمایش سایت

رنگ سایت
حالت نمایش
اندازهٔ متن
خوانایی

این تنظیمات فقط روی مرورگر شما ذخیره می‌شود.