الفصل التاسع: معالجة الأخطاء والاستثناءات (Exception Handling)
مقدمة الفصل
لا يوجد مبرمج في العالم يكتب كودًا خاليًا من الأخطاء تمامًا. الأخطاء هي جزء طبيعي من عملية البرمجة. يمكن أن تحدث لأسباب عديدة: المستخدم قد يُدخل قيمة غير متوقعة، قد تحاول فتح ملف غير موجود، أو قد تقوم بعملية حسابية غير ممكنة مثل القسمة على صفر.
عندما يحدث خطأ أثناء تشغيل برنامج بايثون، فإنه “يثير استثناءً” (raises an exception). إذا لم يتم التعامل مع هذا الاستثناء، سيتوقف برنامجك عن العمل فجأة، وهو ما يترك انطباعًا سيئًا لدى المستخدم.
في هذا الفصل، ستتعلم كيف تكتب برامج قوية ومرنة، قادرة على “التقاط” هذه الاستثناءات والتعامل معها بأناقة، بدلاً من الانهيار. هذه المهارة هي إحدى العلامات الفارقة التي تميز المبرمج المحترف عن المبتدئ.
1. ما هو الاستثناء؟ وأشهر أنواعه
الاستثناء هو حدث (خطأ) يعترض السير الطبيعي لبرنامجك. بايثون تحتوي على العديد من أنواع الاستثناءات المدمجة، ولكل منها اسم يدل على طبيعة الخطأ.
أشهر أنواع الاستثناءات التي ستواجهها:
اسم الاستثناء | سبب الحدوث | مثال |
---|---|---|
ValueError |
عندما تكون قيمة البيانات غير مناسبة (مثل محاولة تحويل نص ليس رقمًا إلى رقم). | int("abc") |
FileNotFoundError |
عند محاولة فتح ملف غير موجود للقراءة. | open("ghost.txt", "r") |
ZeroDivisionError |
عند محاولة قسمة أي عدد على صفر. | 10 / 0 |
TypeError |
عند محاولة إجراء عملية على أنواع بيانات غير متوافقة. | "Hello" + 5 |
IndexError |
عند محاولة الوصول إلى عنصر في قائمة باستخدام فهرس خارج النطاق. | my_list = [1,2]; print(my_list[2]) |
2. التقاط الأخطاء باستخدام try
و except
لتجنب توقف البرنامج، يمكننا وضع الكود الذي نشك أنه قد يسبب خطأً داخل كتلة try
. وإذا حدث خطأ بالفعل، فسيتم تنفيذ الكود الموجود داخل كتلة except
المرتبطة به.
البنية الأساسية:
try:
# Code that might cause an error
# الكود الذي قد يسبب خطأ
except:
# Code to run if an error occurs
# الكود الذي سينفذ في حال حدوث الخطأ
مثال عملي: لنتخيل أننا نطلب من المستخدم إدخال عمره. المستخدم قد يخطئ ويدخل نصًا بدلاً من رقم.
try:
# We try to convert the user's input to an integer
# نحاول تحويل مدخلات المستخدم إلى عدد صحيح
age_str = input("Enter your age: ")
age = int(age_str)
print(f"Next year, you will be {age + 1} years old.")
except:
# This block runs if the int() conversion fails
# هذه الكتلة تعمل إذا فشلت عملية التحويل بـ int
print("Invalid input. Please enter a valid number for your age.")
شرح الكود بالتفصيل:
try:
: نضع الكود “الخطير” داخل هذه الكتلة. في هذه الحالة، السطرage = int(age_str)
هو مصدر الخطر، لأنه سيفشل إذا أدخل المستخدم نصًا مثل “عشرون”.except:
: إذا فشل أي سطر داخل كتلةtry
وأثار استثناءً، فإن بايثون تتوقف فورًا عن تنفيذ بقية كتلةtry
وتقفز مباشرة لتنفيذ الكود الموجود داخل كتلةexcept
.- بهذه الطريقة، بدلاً من أن يتوقف البرنامج ويعرض رسالة خطأ حمراء غير مفهومة للمستخدم، فإنه يعرض رسالة واضحة ولطيفة تخبر المستخدم بكيفية تصحيح خطئه، ثم يواصل عمله بشكل طبيعي (أو ينتهي بأمان).
3. التعامل مع أنواع محددة من الأخطاء
الكتلة except:
العامة تلتقط أي نوع من الأخطاء، لكن هذا ليس دائمًا السلوك المرغوب. من الأفضل تحديد نوع الخطأ الذي تتوقع حدوثه بالضبط. هذا يجعل كودك أكثر دقة ووضوحًا، ويمنع إخفاء أخطاء غير متوقعة.
مثال: آلة حاسبة آمنة
هذا البرنامج قد يواجه خطأين محتملين: ValueError
إذا أدخل المستخدم نصًا، و ZeroDivisionError
إذا حاول القسمة على صفر.
try:
# Get two numbers from the user
# الحصول على رقمين من المستخدم
num1 = float(input("Enter the first number: "))
num2 = float(input("Enter the second number: "))
# Perform the division
# إجراء عملية القسمة
result = num1 / num2
print(f"The result is: {result}")
except ValueError:
# This block only runs if the float() conversion fails
# هذه الكتلة تعمل فقط إذا فشلت عملية التحويل بـ float
print("Error: Please enter valid numbers only.")
except ZeroDivisionError:
# This block only runs if the user tries to divide by zero
# هذه الكتلة تعمل فقط إذا حاول المستخدم القسمة على صفر
print("Error: Cannot divide by zero.")
شرح الكود بالتفصيل:
يمكنك إضافة أي عدد من كتل except
بعد try
. عندما يحدث خطأ، سيبحث بايثون عن أول كتلة except
تطابق نوع الخطأ الذي حدث ويقوم بتنفيذها. هذا يسمح لك بتخصيص رسائل مختلفة واستجابات مختلفة لكل نوع من المشاكل المحتملة.
4. استخدام else
و finally
يمكن توسيع بنية try...except
لتشمل كتلتين إضافيتين اختياريتين: else
و finally
.
else
: الكود الموجود داخل هذه الكتلة يتم تنفيذه فقط إذا لم يحدث أي خطأ في كتلةtry
.finally
: الكود الموجود داخل هذه الكتلة يتم تنفيذه دائمًا، سواء حدث خطأ أم لا. غالبًا ما تُستخدم لعمليات “التنظيف”، مثل إغلاق ملف أو اتصال بقاعدة بيانات.
try:
# Attempt to read a number
# محاولة قراءة رقم
num = int(input("Enter a number: "))
except ValueError:
# Runs only if a ValueError occurs
# تعمل فقط في حال حدوث ValueError
print("That was not a valid number.")
else:
# Runs only if NO exception occurred
# تعمل فقط في حال عدم حدوث أي استثناء
print(f"You entered the number {num}.")
finally:
# Always runs, regardless of what happened
# تعمل دائمًا، بغض النظر عما حدث
print("Attempt finished.")
شرح الكود بالتفصيل:
- إذا أدخل المستخدم رقمًا صحيحًا (مثل
10
): سيتم تنفيذ كتلةtry
بنجاح، ثم كتلةelse
، ثم كتلةfinally
. - إذا أدخل المستخدم نصًا (مثل
abc
): سيفشلtry
، وسيتم تنفيذ كتلةexcept ValueError
، ثم كتلةfinally
.
5. تمرين تطبيقي: تحسين برنامج دفتر الملاحظات
المطلوب:
في الفصل السابق، قمنا ببناء برنامج بسيط لدفتر الملاحظات. الآن، سنقوم بتحسينه ليكون أكثر قوة. سنضيف خيارًا جديدًا وهو “عرض الملاحظات”. يجب أن يتعامل هذا الخيار مع الحالة التي يحاول فيها المستخدم عرض الملاحظات ولكن الملف notes.txt
غير موجود بعد.
إرشادات الحل:
- أضف خيارًا جديدًا للقائمة: في البرنامج، أضف خيارًا رقم “2” في القائمة لعرض الملاحظات.
- استخدم
try...except
: عند تنفيذ خيار عرض الملاحظات، ضع الكود الذي يقرأ الملف داخل كتلةtry
. - التقط الخطأ الصحيح: في كتلة
except
، التقط نوع الخطأ المحددFileNotFoundError
. - اعرض رسالة مناسبة: إذا تم التقاط الخطأ، اطبع رسالة واضحة للمستخدم مثل “لا توجد ملاحظات لعرضها بعد. حاول إضافة ملاحظة أولاً.”
الحل المقترح خطوة بخطوة:
الكود الأصلي الذي سنعدل عليه (جزء الإضافة):
# ... (الكود الخاص بالقائمة الرئيسية) ...
if choice == '1':
note_text = input("Please enter your note: ")
with open("notes.txt", "a", encoding="utf-8") as f:
f.write(note_text + "\n")
print("Your note has been saved successfully!")
# ... (بقية الكود) ...
الخطوة 1: إضافة خيار العرض مع معالجة الأخطاء
# ...
elif choice == '2':
# We will try to open and read the file
# سنحاول فتح وقراءة الملف
try:
with open("notes.txt", "r", encoding="utf-8") as f:
# Read all lines and print them
# قراءة كل السطور وطباعتها
notes = f.read()
print("\n--- Your Notes ---")
# Check if the notes are empty
if notes:
print(notes)
else:
print("The notes file is empty.")
# If the file does not exist, this block will run
# إذا لم يكن الملف موجودًا، ستعمل هذه الكتلة
except FileNotFoundError:
print("No notes found. Try adding a note first.")
# ...
شرح الخطوة 1:
- أضفنا كتلة
elif choice == '2':
للتعامل مع خيار المستخدم الجديد. - وضعنا الكود المسؤول عن قراءة الملف (
with open(...)
) داخل كتلةtry
. هذا هو الكود الذي قد يفشل إذا لم يكن الملف موجودًا. - أضفنا
except FileNotFoundError:
. هذه الكتلة سيتم تنفيذها فقط إذا كان نوع الخطأ هوFileNotFoundError
. - داخل
except
، نطبع رسالة مفيدة للمستخدم ترشده إلى ما يجب عليه فعله.
الكود الكامل المجمع:
# --- Improved Note-Taking Program ---
while True:
print("\n--- My Notebook ---")
print("1. Add a new note")
print("2. View all notes")
print("3. Exit")
choice = input("Enter your choice: ")
if choice == '1':
note_text = input("Please enter your note: ")
with open("notes.txt", "a", encoding="utf-8") as f:
f.write(note_text + "\n")
print("Your note has been saved successfully!")
elif choice == '2':
try:
with open("notes.txt", "r", encoding="utf-8") as f:
notes = f.read()
print("\n--- Your Notes ---")
if notes.strip(): # strip() to handle files with only whitespace
print(notes)
else:
print("The notes file is empty.")
except FileNotFoundError:
print("\nNo notes found. Try adding a note first.")
elif choice == '3':
print("Goodbye!")
break
else:
print("Invalid choice. Please try again.")
6. خلاصة الفصل
- معالجة الاستثناءات هي تقنية أساسية لكتابة برامج موثوقة لا تتوقف عن العمل بشكل مفاجئ.
- استخدم
try
لوضع الكود الذي قد يسبب خطأ. - استخدم
except
لتحديد ما يجب فعله عند حدوث خطأ. من الأفضل دائمًا تحديد نوع الخطأ الذي تتوقعه. - استخدم
else
لتنفيذ كود فقط في حالة عدم حدوث أخطاء. - استخدم
finally
لتنفيذ كود “تنظيف” سيتم تشغيله دائمًا.
ماذا بعد؟
الآن بعد أن تعلمت كيفية بناء برامج قوية ومنظمة، حان الوقت للانتقال إلى أحد أقوى نماذج البرمجة وأكثرها استخدامًا. في الفصل العاشر، سنغوص في عالم البرمجة كائنية التوجه (Object-Oriented Programming - OOP)، حيث ستتعلم كيفية التفكير في برامجك كمجموعة من الكائنات المتفاعلة.