مقالات مختارة

else وfinally وraise في Python: إدارة الاستثناءات بعد try وexcept | أساسيات بايثون 23

else وfinally وraise في Python لإدارة الاستثناءات المتقدمة

إذا كنت تعرف بالفعل الشكل الأساسي لـ 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("انتهت المحاولة")
مثال بسيط يوضح استخدام try و except للتعامل مع الأخطاء في Python

استخدام أكثر من 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)
شرح التعامل مع خطأ القسمة على صفر ZeroDivisionError في Python باستخدام try except

التقاط رسالة الخطأ باستخدام as e

الجزء as e لا يغيّر نوع الاستثناء؛ هو فقط يحفظ كائن الاستثناء في متغير تستطيع قراءة رسالته منه. يمكنك اختيار اسم أوضح مثل error بدل e.

try:
    age = int("twenty")
except ValueError as error:
    print("نوع الخطأ:", type(error).__name__)
    print("الرسالة:", error)

هذا مفيد أثناء التشخيص أو التسجيل، لكن لا تعرض رسالة استثناء داخلية للمستخدم النهائي بدون حاجة. للمستخدم اكتب رسالة مفهومة، وللتصحيح احتفظ بالتفاصيل التقنية.

مثال يوضح التعامل مع ValueError عند إدخال نص بدل رقم في Python

لماذا نستخدم 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.

ملخص تدفق try وexcept وelse وfinally وraise في Python

مقارنة سريعة: ماذا أستخدم؟

الحاجة الأداة المناسبة
معالجة نوع خطأ محدد 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 الرسمية

الخلاصة

بعد فهم 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، لكنها ما تزال واسعة. الأفضل التقاط النوع المحدد الذي تستطيع التعامل معه كلما أمكن.

Admin
Admin
تعليقات