إذا كنت تعرف بالفعل الشكل الأساسي لـ try وexcept، فالخطوة التالية هي فهم كيف تدير تدفق الاستثناءات بطريقة أدق: متى تستخدم else، وماذا تضع داخل finally، وكيف تطلق استثناءً بنفسك باستخدام raise، وكيف تتعامل مع أكثر من نوع خطأ بدون إخفاء مشاكل البرنامج.
هذا الدرس من أساسيات بايثون 23 لا يعيد شرح try/except من الصفر. إذا كنت تريد الأساسيات أولًا، راجع شرح try وexcept في Python للمبتدئين. هنا سنركز على الاستخدام الذي يأتي بعد ذلك: else وfinally وraise وas e وطرق التعامل مع عدة استثناءات.
الفكرة الأساسية: اجعل try صغيرًا ومحددًا، عالج الاستثناءات المتوقعة فقط، ضع كود النجاح في else عندما يكون ذلك أوضح، واستخدم finally للتنظيف الذي يجب تنفيذه عند مغادرة الكتلة.
تدفق try وexcept وelse وfinally في Python
يمكن أن تتكوّن كتلة معالجة الاستثناءات من أربعة أجزاء، ولكل جزء وظيفة مختلفة:
| الجزء | متى يعمل؟ | الاستخدام العملي |
|---|---|---|
try |
يُنفّذ أولًا | ضع فيه العملية التي قد ترفع استثناءً متوقعًا. |
except |
عند حدوث استثناء مطابق | عالج نوع الخطأ المتوقع برسالة أو حل مناسب. |
else |
إذا انتهت try بدون استثناء |
ضع فيه الكود الذي يعتمد على نجاح العملية. |
finally |
عند مغادرة عبارة try في التدفق الطبيعي لبايثون |
نفّذ أعمال التنظيف مثل تحرير مورد تم فتحه يدويًا. |
try:
number = int("25")
except ValueError:
print("القيمة غير صالحة")
else:
print("تم التحويل:", number)
finally:
print("انتهت المحاولة")
استخدام أكثر من except لمعالجة أخطاء مختلفة
إذا كانت العملية قد تفشل بأكثر من طريقة، يمكنك كتابة أكثر من except. هذا أفضل من وضع رسالة واحدة غامضة لكل الأخطاء.
try:
number = int("10")
result = 100 / number
except ValueError:
print("القيمة ليست رقمًا صحيحًا")
except ZeroDivisionError:
print("لا يمكن القسمة على صفر")
else:
print(result)
بهذا الأسلوب يحصل كل خطأ على معالجة تناسبه، ويبقى سلوك البرنامج واضحًا أثناء التصحيح.
متى أجمع أكثر من استثناء في except واحدة؟
إذا كانت عدة استثناءات تحتاج نفس المعالجة تمامًا، يمكنك جمعها داخل tuple:
try:
value = int("abc")
result = 10 / value
except (ValueError, ZeroDivisionError) as error:
print("تعذر إكمال العملية:", error)
التقاط رسالة الخطأ باستخدام as e
الجزء as e لا يغيّر نوع الاستثناء؛ هو فقط يحفظ كائن الاستثناء في متغير تستطيع قراءة رسالته منه. يمكنك اختيار اسم أوضح مثل error بدل e.
try:
age = int("twenty")
except ValueError as error:
print("نوع الخطأ:", type(error).__name__)
print("الرسالة:", error)
هذا مفيد أثناء التشخيص أو التسجيل، لكن لا تعرض رسالة استثناء داخلية للمستخدم النهائي بدون حاجة. للمستخدم اكتب رسالة مفهومة، وللتصحيح احتفظ بالتفاصيل التقنية.
لماذا نستخدم else بعد try وexcept؟
تعمل else فقط عندما تنتهي try بدون استثناء. فائدتها ليست مجرد الاختصار؛ بل تساعدك على إبقاء الجزء المحمي داخل try صغيرًا، فلا تلتقط بالخطأ استثناءً نتج من كود النجاح نفسه.
try:
number = int("12")
except ValueError as error:
print("تعذر التحويل:", error)
else:
square = number * number
print("المربع:", square)
لو وضعت حساب square داخل try بدون حاجة، فقد تصبح منطقة التقاط الأخطاء أكبر من اللازم. لهذا توصي وثائق Python باستخدام else عندما يكون لديك كود يجب تشغيله فقط بعد نجاح العملية المحمية.
استخدام finally لتنظيف الموارد
تُنفّذ finally عند مغادرة عبارة try ضمن تدفق التنفيذ المعتاد لبايثون، سواء تم التعامل مع الاستثناء أو استمر للخارج. لذلك تستخدم عادةً في cleanup عندما تكون قد فتحت موردًا يدويًا ويجب إغلاقه.
file = None
try:
file = open("data.txt", "r", encoding="utf-8")
content = file.read()
except FileNotFoundError as error:
print("لم يتم العثور على الملف:", error)
else:
print(content)
finally:
if file is not None:
file.close()
لكن مع الملفات، الطريقة الأنظف غالبًا هي استخدام with لأنها تدير الإغلاق تلقائيًا:
try:
with open("data.txt", "r", encoding="utf-8") as file:
content = file.read()
except FileNotFoundError:
print("الملف غير موجود")
else:
print(content)
راجع أيضًا التعامل مع الملفات في Python إذا كنت تريد تفاصيل أكبر عن with والقراءة والكتابة.
استخدام raise لإطلاق استثناء بنفسك
لا تستخدم raise لمعالجة خطأ موجود، بل لإعلان أن البرنامج وصل إلى حالة غير مقبولة. مثال شائع هو التحقق من قيمة مدخلة داخل دالة.
def set_age(age):
if age < 0:
raise ValueError("العمر لا يمكن أن يكون سالبًا")
return age
إذا استدعيت set_age(-5) فسيرفع البرنامج ValueError برسالة واضحة. ويمكن للجزء الذي استدعى الدالة أن يقرر هل يعالج الخطأ أم يسمح له بالانتقال إلى مستوى أعلى.
التقاط الاستثناء الذي رفعته بنفسك
try:
age = set_age(-5)
except ValueError as error:
print("قيمة غير صالحة:", error)
إعادة رفع الاستثناء باستخدام raise وحدها
أحيانًا تريد تسجيل الخطأ أو إضافة معلومة ثم السماح له بالاستمرار بدل ابتلاعه. داخل except يمكنك كتابة raise بدون اسم لإعادة رفع الاستثناء الحالي:
try:
number = int("abc")
except ValueError as error:
print("فشل التحويل:", error)
raise
هذا مفيد في التسجيل أو عند وجود طبقة أعلى مسؤولة عن القرار النهائي بشأن الخطأ.
متى لا تستخدم except عامة؟
كتابة except: بدون تحديد نوع قد تلتقط أشياء لا تقصدها، ومنها استثناءات مخصصة لإنهاء البرنامج مثل KeyboardInterrupt وSystemExit. لذلك ابدأ بالاستثناءات المحددة التي تتوقعها.
# أفضل عندما تعرف الخطأ المتوقع
try:
number = int("abc")
except ValueError as error:
print(error)
أما except Exception as error: فهو أضيق من bare except: لأنه لا يلتقط عادةً استثناءات إنهاء البرنامج التي ترث مباشرة من BaseException، لكنه يظل واسعًا جدًا؛ لا تستخدمه كبديل عن معرفة الاستثناءات المتوقعة.
تجنب: except: pass أو except Exception: pass لمجرد إخفاء المشكلة. إذا احتجت catch واسعًا للتسجيل، فكر في إعادة رفع الخطأ بعد تسجيله.
مثال كامل: multiple except وelse وfinally وraise
def calculate_price(price_text, quantity):
try:
price = float(price_text)
if price < 0:
raise ValueError("السعر لا يمكن أن يكون سالبًا")
if quantity <= 0:
raise ValueError("الكمية يجب أن تكون أكبر من صفر")
except (TypeError, ValueError) as error:
print("تعذر حساب السعر:", error)
return None
else:
return price * quantity
finally:
print("انتهت محاولة حساب السعر")
هذا المثال يجمع الأجزاء معًا بدون استخدام catch عام: التحويل داخل try، التحقق بـraise، معالجة الأخطاء المتوقعة داخل except، النتيجة الناجحة داخل else، وأمر النهاية داخل finally.
مقارنة سريعة: ماذا أستخدم؟
| الحاجة | الأداة المناسبة |
|---|---|
| معالجة نوع خطأ محدد | except ValueError أو النوع المتوقع |
| معالجة عدة أنواع بالطريقة نفسها | except (ValueError, TypeError) |
| قراءة رسالة الاستثناء | except ValueError as error |
تشغيل كود بعد نجاح try |
else |
| تنظيف مورد عند مغادرة الكتلة | finally أو context manager مثل with |
| رفض قيمة أو حالة غير صحيحة بنفسك | raise |
| تسجيل الخطأ ثم تمريره للطبقة الأعلى | raise بدون اسم داخل except |
أفضل ممارسات إدارة الاستثناءات
- التقط الاستثناء الأكثر تحديدًا الذي تستطيع التعامل معه فعلًا.
- اجعل كتلة
tryصغيرة حتى لا تلتقط أخطاء من أسطر لم تقصد حمايتها. - استخدم
elseللكود الذي يجب تشغيله بعد نجاح العملية إذا كان ذلك يجعل حدودtryأوضح. - استخدم
finallyللتنظيف، لكن فضّلwithعندما يكون المورد يدعم context manager. - استخدم
raiseللتعبير عن حالة غير صالحة، ولا تستخدم الاستثناءات بدل الشروط الطبيعية في كل الحالات. - لا تستخدم bare
except:أوexcept: passلإخفاء أخطاء لا تفهمها.
مصادر Python الرسمية
- Errors and Exceptions — Python Documentation
- The try statement — Python Language Reference
- Built-in Exceptions — Python Documentation
الخلاصة
بعد فهم try وexcept الأساسيين، تصبح إدارة الاستثناءات أوضح عندما تفصل مسؤولية كل جزء: عالج الأخطاء المتوقعة في except، ضع كود النجاح في else عند الحاجة، استخدم finally للتنظيف، وارفع الاستثناء بنفسك بـraise عندما تكتشف قيمة أو حالة غير مقبولة.
والقاعدة الأهم: لا توسّع except أكثر مما تحتاج. كلما كان نوع الاستثناء محددًا وحدود try أصغر، كان تشخيص الأخطاء وصيانة البرنامج أسهل.
قاعدة عملية: تعامل مع الاستثناء الذي تعرف كيف تستجيب له، واترك الأخطاء غير المتوقعة تظهر بدل إخفائها.
أسئلة شائعة
ما الفرق بين else وfinally مع try except؟
else يعمل عندما تنتهي try بدون استثناء، بينما finally يُستخدم للتنظيف الذي يجب تنفيذه عند مغادرة عبارة try سواء نجحت العملية أو حدث استثناء.
كيف أتعامل مع أكثر من Exception في Python؟
استخدم عدة كتل except إذا كان لكل خطأ معالجة مختلفة، أو اجمع الأنواع داخل tuple مثل except (ValueError, TypeError) as error: إذا كانت المعالجة نفسها.
ما معنى Exception as e؟
تعني حفظ كائن الاستثناء في متغير اسمه e حتى تستطيع قراءة رسالته أو تفاصيله. ويمكن اختيار اسم أوضح مثل error.
متى أستخدم raise في Python؟
استخدم raise عندما يكتشف الكود حالة غير صحيحة ويجب إبلاغ المستدعي بها، مثل عمر سالب أو قيمة لا تحقق شروط الدالة.
هل finally ضرورية لإغلاق الملفات؟
يمكن استخدام finally لذلك، لكن عند التعامل مع الملفات تكون with open(...) غالبًا أبسط لأنها تغلق الملف تلقائيًا عبر context manager.
هل except Exception أفضل من except بدون نوع؟
هي أضيق من bare except: لأنها لا تلتقط عادةً استثناءات مثل KeyboardInterrupt وSystemExit، لكنها ما تزال واسعة. الأفضل التقاط النوع المحدد الذي تستطيع التعامل معه كلما أمكن.




