معالجة الأخطاء (Error Handling)
لا يوجد مبرمج لا يرتكب أخطاء. لكن المبرمج المحترف هو من يتوقع الأخطاء ويعالجها قبل أن تتسبب في انهيار البرنامج. في بايثون، نستخدم آلية معالجة الاستثناءات (Exception Handling) للسيطرة على الأخطاء التي تحدث أثناء تشغيل البرنامج (Runtime Errors)، مثل محاولة القسمة على صفر أو فتح ملف غير موجود.
1. أنواع الأخطاء
بشكل عام، هناك نوعان رئيسيان من الأخطاء:
- أخطاء القواعد (Syntax Errors): يكتشفها المفسر قبل التشغيل (مثل نسيان نقطتين
:). لا يمكن معالجتها بـtry-except. - أخطاء التشغيل (Exceptions): تحدث أثناء عمل البرنامج (مثل إدخال نص بدلاً من رقم). هذه ما سنعالجه اليوم.
2. كتلة try...except
نضع الكود الذي نتوقع حدوث خطأ فيه داخل try، ونضع كود المعالجة داخل except.
try:
# كود قد يسبب خطأ
number = int(input("أدخل رقماً: "))
print(f"الرقم هو: {number}")
except:
# هذا الكود يعمل فقط إذا حدث خطأ
print("خطأ! يجب أن تدخل أرقاماً فقط.")
print("البرنامج مستمر...")
3. تحديد نوع الخطأ (Specific Exceptions)
من الأفضل دائماً تحديد نوع الخطأ الذي نريد اصطياده، بدلاً من استخدام except عامة.
try:
x = 10 / 0
except ZeroDivisionError:
print("لا يمكن القسمة على صفر!")
except ValueError:
print("قيمة غير صحيحة!")
# يمكنك اصطياد عدة أخطاء معاً
try:
num = int(input("أدخل رقماً للقسمة عليه: "))
result = 100 / num
print(result)
except (ValueError, ZeroDivisionError) as e:
print(f"حدث خطأ: {e}")
4. كتل else و finally
else: تعمل إذا لم يحدث أي خطأ في الـtry.finally: تعمل دائماً، سواء حدث خطأ أم لا (مفيدة لإغلاق الملفات أو الاتصالات).
try:
print("1. فتح الملف...")
# محاكاة عملية
result = 10 / 2
except ZeroDivisionError:
print("2. حدث خطأ!")
else:
print("2. العملية تمت بنجاح!")
finally:
print("3. إغلاق الملف (يعمل دائماً)")
5. إثارة الأخطاء يدوياً (raise)
أحياناً تريد أنت أن توقف البرنامج إذا خالف المستخدم شرطاً معيناً.
age = -5
if age < 0:
raise ValueError("العمر لا يمكن أن يكون سالباً!")
6. تطبيق عملي: حاسبة آمنة
def safe_divide():
while True:
try:
num1 = float(input("الرقم الأول: "))
num2 = float(input("الرقم الثاني: "))
result = num1 / num2
except ValueError:
print("❌ خطأ: الرجاء إدخال أرقام فقط!")
except ZeroDivisionError:
print("❌ خطأ: لا يمكن القسمة على صفر!")
else:
print(f"✅ النتيجة: {result}")
break # الخروج من الحلقة عند النجاح
finally:
print("-" * 20)
safe_divide()
7. مديرو السياق (Context Managers)
بدلاً من استخدام try-finally في كل مكان، بايثون توفر "مديرو السياق" (Context Managers) باستخدام كلمة with. هذه تضمن تنفيذ كود التنظيف (cleanup) تلقائياً حتى لو حدث خطأ.
# مثال 1: فتح وإغلاق ملف بأمان
with open("test.txt", "w") as f:
f.write("مرحباً")
# الملف يُغلق تلقائياً هنا، حتى لو حدث خطأ
# مثال 2: اتصال قاعدة بيانات
# (بافتراض وجود مكتبة للاتصال)
with database_connection() as db:
db.query("SELECT * FROM users")
# الاتصال يُغلق تلقائياً
# مثال 3: Lock في البرامج متعددة الخيوط
with threading.Lock() as lock:
# كود حماية بموارد مشتركة
pass
# يتم تحرير القفل تلقائياً
إنشاء Context Manager خاص بك
يمكنك إنشاء context manager خاص بك باستخدام الديكوريتور @contextmanager:
from contextlib import contextmanager
@contextmanager
def database_connection():
"""Context manager لاتصالات قاعدة البيانات"""
print("✅ فتح الاتصال...")
try:
# هنا سيتم فتح الاتصال
yield "connection_object"
finally:
print("❌ إغلاق الاتصال...")
# الاستخدام
with database_connection() as conn:
print(f"استخدام: {conn}")
# النتيجة:
# ✅ فتح الاتصال...
# استخدام: connection_object
# ❌ إغلاق الاتصال...
- تضمن تنفيذ كود التنظيف دائماً
- تقلل من الأخطاء المتعلقة بنسيان إغلاق الموارد
- تجعل الكود أكثر وضوحاً وسهولة في القراءة
ملخص الدرس
- استخدم
try-exceptلحماية برنامجك من التوقف المفاجئ. - حدد دائماً نوع الخطأ المتوقع (مثل
ValueError). - استخدم
finallyأوwithلتنظيف الموارد (مثل إغلاق الملفات). with(Context Managers) هي الطريقة الحديثة والأفضل لإدارة الموارد.