معالجة الأخطاء — هياكل البيانات
معالجة الأخطاء (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)
اختبر فهمك