Kodax

معالجة الأخطاء — هياكل البيانات

معالجة الأخطاء (Exceptions)

بعض الأخطاء ليست أخطاء في كودك بل ظروف متوقعة: المستخدم أدخل نصاً بدل رقم، الملف غير موجود، الاتصال انقطع. بدلاً من أن يتوقف البرنامج، نلتقط الخطأ ونتعامل معه.

try و except

try:
    age = int(input("العمر: "))
except ValueError:
    print("أدخل رقماً صحيحاً")

إذا حدث ValueError داخل try، ينتقل التنفيذ فوراً إلى except ولا يُكمل بقية try.

التقط أخطاء محددة

text = "0"
try:
    result = 10 / int(text)
except ValueError:
    print("ليس رقماً")
except ZeroDivisionError:
    print("لا يمكن القسمة على صفر")

تجنب except: الفارغة أو except Exception لكل شيء: ستخفي أخطاء حقيقية في الكود (مثل خطأ إملائي في اسم متغير) وتجعل اكتشافها صعباً جداً. التقط فقط ما تتوقعه وتعرف كيف تتعامل معه.

الوصول لتفاصيل الخطأ

except ValueError as error:
    print("التفاصيل:", error)

else و finally

text = "25"
try:
    value = int(text)
except ValueError:
    print("خطأ")
else:
    print("نجح التحويل:", value)   # فقط إذا لم يحدث خطأ
finally:
    print("يُنفَّذ دائماً")         # للتنظيف: إغلاق اتصال، حذف ملف مؤقت

ضع في try أقل كود ممكن (الجزء الذي قد يفشل فقط)، وضع ما يعتمد على نجاحه في else.

رفع الأخطاء بنفسك: raise

عندما تستقبل دالتك قيمة غير صالحة، أوقفها برسالة واضحة بدل إرجاع نتيجة خاطئة:

def set_age(age):
    if age < 0:
        raise ValueError("العمر لا يمكن أن يكون سالباً")
    return age

أخطاء مخصصة

class InsufficientFundsError(Exception):
    pass

تسمية الخطأ تجعل الكود الذي يستدعي دالتك يلتقطه بدقة.

LBYL أم EAFP؟

أسلوب Python المفضل غالباً: "جرّب ثم عالج الخطأ" (EAFP) بدل "افحص كل شيء قبل المحاولة". مثلاً try: d[key] بدل if key in d عندما يكون الغياب حالة نادرة.

تمرين

اكتب دالة safe_divide(a, b) ترجع الناتج، أو ترمي ValueError برسالة واضحة إذا كان b صفراً. ثم اكتب برنامجاً يطلب رقمين ويستخدمها ويتعامل مع كل الأخطاء الممكنة.

def to_int(text):
    """تحويل آمن مع رسالة واضحة"""
    try:
        return int(text)
    except ValueError as error:
        print(f"تعذر التحويل: {error}")
        return None

print(to_int("42"), to_int("abc"))

# أخطاء متعددة محددة
def divide(a_text, b_text):
    try:
        a, b = int(a_text), int(b_text)
        result = a / b
    except ValueError:
        return "أدخل أرقاماً صحيحة"
    except ZeroDivisionError:
        return "لا يمكن القسمة على صفر"
    else:
        return f"الناتج: {result:.2f}"
    finally:
        print(f"[محاولة قسمة {a_text} على {b_text}]")

print(divide("10", "4"))
print(divide("10", "0"))
print(divide("x", "2"))

# raise للتحقق من المدخلات
def set_age(age):
    if not isinstance(age, int):
        raise TypeError("العمر يجب أن يكون رقماً صحيحاً")
    if age < 0:
        raise ValueError("العمر لا يمكن أن يكون سالباً")
    return age

for value in [25, -3, "عشرون"]:
    try:
        print("عمر صالح:", set_age(value))
    except (ValueError, TypeError) as error:
        print("مرفوض:", error)

# خطأ مخصص
class InsufficientFundsError(Exception):
    pass

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFundsError(f"الرصيد {balance} لا يكفي لسحب {amount}")
    return balance - amount

try:
    withdraw(100, 250)
except InsufficientFundsError as error:
    print(error)
اختبر فهمك